Browse Source

rewrite with Hooks

iou90 5 years ago
parent
commit
1481190012
3 changed files with 173 additions and 256 deletions
  1. 86
    102
      autoHeightWebView/index.js
  2. 0
    22
      autoHeightWebView/momoize.js
  3. 87
    132
      autoHeightWebView/utils.js

+ 86
- 102
autoHeightWebView/index.js View File

@@ -1,6 +1,6 @@
1 1
 'use strict';
2 2
 
3
-import React, { PureComponent } from 'react';
3
+import React, { useState, useEffect, useMemo, useRef, useImperativeHandle, forwardRef } from 'react';
4 4
 
5 5
 import { StyleSheet, Platform, ViewPropTypes } from 'react-native';
6 6
 
@@ -8,68 +8,21 @@ import PropTypes from 'prop-types';
8 8
 
9 9
 import { WebView } from 'react-native-webview';
10 10
 
11
-import {
12
-  isEqual,
13
-  setState,
14
-  getWidth,
15
-  isSizeChanged,
16
-  handleSizeUpdated,
17
-  getStateFromProps,
18
-  getBaseScript
19
-} from './utils';
20
-
21
-import momoize from './momoize';
22
-
23
-export default class AutoHeightWebView extends PureComponent {
24
-  static propTypes = {
25
-    onSizeUpdated: PropTypes.func,
26
-    // 'web/' by default on iOS
27
-    // 'file:///android_asset/web/' by default on Android
28
-    baseUrl: PropTypes.string,
29
-    // add baseUrl/files... to android/app/src/assets/ on android
30
-    // add baseUrl/files... to project root on iOS
31
-    files: PropTypes.arrayOf(
32
-      PropTypes.shape({
33
-        href: PropTypes.string,
34
-        type: PropTypes.string,
35
-        rel: PropTypes.string
36
-      })
37
-    ),
38
-    style: ViewPropTypes.style,
39
-    customScript: PropTypes.string,
40
-    customStyle: PropTypes.string,
41
-    // webview props
42
-    originWhitelist: PropTypes.arrayOf(PropTypes.string),
43
-    onMessage: PropTypes.func
44
-  };
45
-
46
-  static defaultProps = {
47
-    baseUrl: Platform.OS === 'ios' ? 'web/' : 'file:///android_asset/web/'
48
-  };
49
-
50
-  constructor(props) {
51
-    super(props);
52
-    const { style } = props;
53
-    this.webView = React.createRef();
54
-    const width = getWidth(style);
55
-    const height = style && style.height ? style.height : 1;
56
-    this.size = {
57
-      oldWidth: width,
58
-      oldHeight: height
59
-    };
60
-    this.state = {
61
-      width,
62
-      height
63
-    };
64
-  }
11
+import { getMemoInputProps, getMemoResult, getWidth, isSizeChanged } from './utils';
65 12
 
66
-  getUpdatedState = momoize(setState, isEqual);
13
+const AutoHeightWebView = forwardRef((props, ref) => {
14
+  let webView = useRef();
15
+  useImperativeHandle(ref, () => ({
16
+    stopLoading: () => webView.current.stopLoading()
17
+  }));
67 18
 
68
-  static getDerivedStateFromProps(props, state) {
69
-    return getStateFromProps(props, state);
70
-  }
19
+  const { style, onMessage, onSizeUpdated } = props;
71 20
 
72
-  onMessage = event => {
21
+  const [size, setSize] = useState(() => ({
22
+    height: style && style.height ? style.height : 1,
23
+    width: getWidth(style)
24
+  }));
25
+  const hanldeMessage = event => {
73 26
     if (!event.nativeEvent) {
74 27
       return;
75 28
     }
@@ -82,55 +35,86 @@ export default class AutoHeightWebView extends PureComponent {
82 35
       return;
83 36
     }
84 37
     const { height, width } = data;
85
-    const { oldHeight, oldWidth } = this.size;
86
-    if (isSizeChanged(height, oldHeight, width, oldWidth)) {
87
-      this.size = {
88
-        oldHeight: height,
89
-        oldWidth: width
90
-      };
91
-      this.setState(
92
-        {
93
-          height,
94
-          width
95
-        },
96
-        () => handleSizeUpdated(height, width, this.props.onSizeUpdated)
97
-      );
98
-    }
99
-    const { onMessage } = this.props;
38
+    const { height: previousHeight, width: previousWidth } = size;
39
+    isSizeChanged({ height, previousHeight, width, previousWidth }) &&
40
+      setSize({
41
+        height,
42
+        width
43
+      });
100 44
     onMessage && onMessage(event);
101 45
   };
102 46
 
103
-  stopLoading() {
104
-    this.webView.current.stopLoading();
105
-  }
47
+  const { source, script } = useMemo(() => getMemoResult(props), [getMemoInputProps(props)]);
106 48
 
107
-  render() {
108
-    const { height, width } = this.state;
109
-    const { style, originWhitelist } = this.props;
110
-    const { source, script } = this.getUpdatedState(this.props, getBaseScript);
111
-    return (
112
-      <WebView
113
-        {...this.props}
114
-        originWhitelist={originWhitelist || ['*']}
115
-        ref={this.webView}
116
-        onMessage={this.onMessage}
117
-        style={[
118
-          styles.webView,
119
-          {
120
-            width,
121
-            height
122
-          },
123
-          style
124
-        ]}
125
-        injectedJavaScript={script}
126
-        source={source}
127
-      />
128
-    );
129
-  }
130
-}
49
+  const { width, height } = size;
50
+  useEffect(
51
+    () =>
52
+      onSizeUpdated &&
53
+      onSizeUpdated({
54
+        height,
55
+        width
56
+      }),
57
+    [width, height]
58
+  );
59
+  return (
60
+    <WebView
61
+      {...props}
62
+      ref={webView}
63
+      onMessage={hanldeMessage}
64
+      style={[
65
+        styles.webView,
66
+        {
67
+          width,
68
+          height
69
+        },
70
+        style
71
+      ]}
72
+      injectedJavaScript={script}
73
+      source={source}
74
+    />
75
+  );
76
+});
77
+
78
+AutoHeightWebView.propTypes = {
79
+  onSizeUpdated: PropTypes.func,
80
+  // 'web/' by default on iOS
81
+  // 'file:///android_asset/web/' by default on Android
82
+  baseUrl: PropTypes.string,
83
+  // add baseUrl/files... to android/app/src/assets/ on android
84
+  // add baseUrl/files... to project root on iOS
85
+  files: PropTypes.arrayOf(
86
+    PropTypes.shape({
87
+      href: PropTypes.string,
88
+      type: PropTypes.string,
89
+      rel: PropTypes.string
90
+    })
91
+  ),
92
+  style: ViewPropTypes.style,
93
+  customScript: PropTypes.string,
94
+  customStyle: PropTypes.string,
95
+  // webview props
96
+  originWhitelist: PropTypes.arrayOf(PropTypes.string),
97
+  onMessage: PropTypes.func
98
+};
99
+
100
+let defaultProps = {
101
+  originWhitelist: ['*'],
102
+  baseUrl: 'web/'
103
+};
104
+
105
+Platform.OS === 'android' &&
106
+  Object.assign(defaultProps, {
107
+    baseUrl: 'file:///android_asset/web/',
108
+    // if set to true may cause some layout issues (width of container will be than width of screen) on android
109
+    scalesPageToFit: false
110
+  });
111
+
112
+AutoHeightWebView.defaultProps = defaultProps;
131 113
 
132 114
 const styles = StyleSheet.create({
133 115
   webView: {
134 116
     backgroundColor: 'transparent'
135 117
   }
136 118
 });
119
+
120
+export default AutoHeightWebView;

+ 0
- 22
autoHeightWebView/momoize.js View File

@@ -1,22 +0,0 @@
1
-'use strict';
2
-
3
-function defaultIsNewArgEqualToLast(newArgs, lastArgs) {
4
-    return newArgs.length === lastArgs.length && newArgs.every((arg, index) => arg === lastArgs[index]);
5
-}
6
-
7
-export default function memoize(resultCallback, isNewArgEqualToLast) {
8
-  let lastArgs = [];
9
-  let lastResult;
10
-  let calledOnce = false;
11
-  const isEqual = isNewArgEqualToLast || defaultIsNewArgEqualToLast;
12
-  const result = function(...newArgs) {
13
-    if (calledOnce && isEqual(newArgs, lastArgs)) {
14
-      return lastResult;
15
-    }
16
-    calledOnce = true;
17
-    lastArgs = newArgs;
18
-    lastResult = resultCallback.apply(this, newArgs);
19
-    return lastResult;
20
-  };
21
-  return result;
22
-}

+ 87
- 132
autoHeightWebView/utils.js View File

@@ -2,119 +2,7 @@
2 2
 
3 3
 import { Dimensions, Platform } from 'react-native';
4 4
 
5
-import Immutable from 'immutable';
6
-
7
-function appendFilesToHead(files, script) {
8
-  return files.reduceRight((combinedScript, file) => {
9
-    const { rel, type, href } = file;
10
-    return `
11
-            var link  = document.createElement('link');
12
-            link.rel  = '${rel}';
13
-            link.type = '${type}';
14
-            link.href = '${href}';
15
-            document.head.appendChild(link);
16
-            ${combinedScript}
17
-          `;
18
-  }, script);
19
-}
20
-
21
-const screenWidth = Dimensions.get('window').width;
22
-
23
-const bodyStyle = `
24
-body {
25
-  margin: 0;
26
-  padding: 0;
27
-}
28
-`;
29
-
30
-function appendStylesToHead(styles, script) {
31
-  const currentStyles = styles ? bodyStyle + styles : bodyStyle;
32
-  // Escape any single quotes or newlines in the CSS with .replace()
33
-  const escaped = currentStyles.replace(/\'/g, "\\'").replace(/\n/g, '\\n');
34
-  return `
35
-          var styleElement = document.createElement('style');
36
-          styleElement.innerHTML = '${escaped}';
37
-          document.head.appendChild(styleElement);
38
-          ${script}
39
-        `;
40
-}
41
-
42
-function getReloadRelatedData(props) {
43
-  const { files, customStyle, customScript, style, source } = props;
44
-  return {
45
-    source,
46
-    files,
47
-    customStyle,
48
-    customScript,
49
-    style
50
-  };
51
-}
52
-
53
-function isChanged(newValue, oldValue) {
54
-  return !Immutable.is(Immutable.fromJS(newValue), Immutable.fromJS(oldValue));
55
-}
56
-
57
-function getInjectedSource(html, script) {
58
-  return `
59
-    ${html}
60
-    <script>
61
-    ${script}
62
-    </script>
63
-    `;
64
-}
65
-
66
-function getScript(props, getScript) {
67
-  const { files, customStyle, customScript, style } = getReloadRelatedData(props);
68
-  let script = getScript(style);
69
-  script = files && files.length > 0 ? appendFilesToHead(files, script) : script;
70
-  script = appendStylesToHead(customStyle, script);
71
-  customScript && (script = customScript + script);
72
-  return script;
73
-}
74
-
75
-export function getWidth(style) {
76
-  return style && style.width ? style.width : screenWidth;
77
-}
78
-
79
-export function isEqual(newProps, oldProps) {
80
-  return isChanged(getReloadRelatedData(newProps), getReloadRelatedData(oldProps));
81
-}
82
-
83
-export function setState(props, getBaseScript) {
84
-  const { source, baseUrl } = props;
85
-  const script = getScript(props, getBaseScript);
86
-  let state = {};
87
-  if (source.html) {
88
-    let currentSource = { html: getInjectedSource(source.html, script) };
89
-    baseUrl && Object.assign(currentSource, { baseUrl });
90
-    Object.assign(state, { source: currentSource });
91
-  } else {
92
-    let currentSource = Object.assign({}, source);
93
-    baseUrl && Object.assign(currentSource, { baseUrl });
94
-    Object.assign(state, {
95
-      source: currentSource,
96
-      script
97
-    });
98
-  }
99
-  return state;
100
-}
101
-
102
-export function handleSizeUpdated(height, width, onSizeUpdated) {
103
-  onSizeUpdated &&
104
-    onSizeUpdated({
105
-      height,
106
-      width
107
-    });
108
-}
109
-
110
-export function isSizeChanged(height, oldHeight, width, oldWidth) {
111
-  if (!height || !width) {
112
-    return;
113
-  }
114
-  return height !== oldHeight || width !== oldWidth;
115
-}
116
-
117
-export const domMutationObserveScript = `
5
+const domMutationObserveScript = `
118 6
 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
119 7
 var observer = new MutationObserver(updateSize);
120 8
 observer.observe(document, {
@@ -123,8 +11,8 @@ observer.observe(document, {
123 11
 });
124 12
 `;
125 13
 
126
-export function updateSizeWithMessage(element) {
127
-  return `
14
+const updateSizeWithMessage = element =>
15
+  `
128 16
   var updateSizeInterval = null;
129 17
   var height = 0;
130 18
   function updateSize(event) {
@@ -138,21 +26,6 @@ export function updateSizeWithMessage(element) {
138 26
     window.ReactNativeWebView.postMessage(JSON.stringify({ width: width, height: height, event: event }));
139 27
   }
140 28
   `;
141
-}
142
-
143
-export function getStateFromProps(props, state) {
144
-  const { height: oldHeight, width: oldWidth } = state;
145
-  const height = props.style ? props.style.height : null;
146
-  const width = props.style ? props.style.width : null;
147
-  if (isSizeChanged(height, oldHeight, width, oldWidth)) {
148
-    return {
149
-      height: height || oldHeight,
150
-      width: width || oldWidth,
151
-      isSizeChanged: true
152
-    };
153
-  }
154
-  return null;
155
-}
156 29
 
157 30
 // add viewport setting to meta for WKWebView
158 31
 const makeScalePageToFit = `
@@ -161,8 +34,8 @@ meta.setAttribute('name', 'viewport');
161 34
 meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);
162 35
 `;
163 36
 
164
-export function getBaseScript(style) {
165
-  return `
37
+const getBaseScript = style =>
38
+  `
166 39
   ;
167 40
   if (!document.getElementById("rnahw-wrapper")) {
168 41
     var wrapper = document.createElement('div');
@@ -180,4 +53,86 @@ export function getBaseScript(style) {
180 53
   ${Platform.OS === 'ios' ? makeScalePageToFit : ''}
181 54
   updateSize();
182 55
   `;
56
+
57
+const appendFilesToHead = ({ files, script }) =>
58
+  files.reduceRight((combinedScript, file) => {
59
+    const { rel, type, href } = file;
60
+    return `
61
+          var link  = document.createElement('link');
62
+          link.rel  = '${rel}';
63
+          link.type = '${type}';
64
+          link.href = '${href}';
65
+          document.head.appendChild(link);
66
+          ${combinedScript}
67
+        `;
68
+  }, script);
69
+
70
+const screenWidth = Dimensions.get('window').width;
71
+
72
+const bodyStyle = `
73
+body {
74
+  margin: 0;
75
+  padding: 0;
183 76
 }
77
+`;
78
+
79
+const appendStylesToHead = ({ style, script }) => {
80
+  const currentStyles = style ? bodyStyle + style : bodyStyle;
81
+  // Escape any single quotes or newlines in the CSS with .replace()
82
+  const escaped = currentStyles.replace(/\'/g, "\\'").replace(/\n/g, '\\n');
83
+  return `
84
+          var styleElement = document.createElement('style');
85
+          styleElement.innerHTML = '${escaped}';
86
+          document.head.appendChild(styleElement);
87
+          ${script}
88
+        `;
89
+};
90
+
91
+const getInjectedSource = ({ html, script }) => `
92
+${html}
93
+<script>
94
+${script}
95
+</script>
96
+`;
97
+
98
+const getScript = props => {
99
+  const { files, customStyle, customScript, style } = props;
100
+  let script = getBaseScript(style);
101
+  script = files && files.length > 0 ? appendFilesToHead({ files, script }) : script;
102
+  script = appendStylesToHead({ style: customStyle, script });
103
+  customScript && (script = customScript + script);
104
+  return script;
105
+};
106
+
107
+export const getWidth = style => {
108
+  return style && style.width ? style.width : screenWidth;
109
+};
110
+
111
+export const isSizeChanged = ({ height, previousHeight, width, previousWidth }) => {
112
+  if (!height || !width) {
113
+    return;
114
+  }
115
+  return height !== previousHeight || width !== previousWidth;
116
+}
117
+
118
+export const getMemoInputProps = props => {
119
+  const { files, customStyle, customScript, style, source, baseUrl } = props;
120
+  return [files, customStyle, customScript, style, source, baseUrl];
121
+};
122
+
123
+export const getMemoResult = props => {
124
+  const { source, baseUrl } = props;
125
+  const script = getScript(props);
126
+  if (source.html) {
127
+    let currentSource = { html: getInjectedSource({ html: source.html, script }) };
128
+    baseUrl && Object.assign(currentSource, { baseUrl });
129
+    return { source: currentSource };
130
+  } else {
131
+    let currentSource = Object.assign({}, source);
132
+    baseUrl && Object.assign(currentSource, { baseUrl });
133
+    return {
134
+      source: currentSource,
135
+      script
136
+    };
137
+  }
138
+};