Original address: github.com/HuJiaoHJ/bl…

This article will share the React Native to Web solution from three aspects: React – Native – Web

  • The react – native – the use of the web
  • React-native Web source code analysis
  • The react – native – web practice

React-native-web:github.com/necolas/rea…

use

The installation

yarn add react react-dom react-native-web
Copy the code

If ART is used, install react-Art (for example, react-nATIVE-SVG is used for icon RN scheme, which is based on React-Art).

yarn add react-art
Copy the code

After installation, the use of the main points in the following two steps:

  • Webpack configuration
  • New configuration at entrance

Webpack configuration

Configure the WebPack configuration as the React Web configuration. Add the alias configuration as follows:

// webpack.config.js
module.exports = {
  / /... the rest of your config
  resolve: {
    alias: {
      'react-native$': 'react-native-web'}}}Copy the code

New configuration at entrance

There are two ways:

  • Using AppRegistry API
  • Using the Render method

Using AppRegistry API

Before adding a configuration, take a look at RN’s entry file:

// index.js
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('rn_web', () => App);
Copy the code

The configuration is as follows:

// index.web.js
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('rn_web', () => App);

AppRegistry.runApplication('rn_web', {
    rootTag: document.getElementById('react-root')});Copy the code

Using the Render method

Use the render method as follows:

import { render } from 'react-native';
import App from './App';

render(<App/>.rootTag: document.getElementById('react-root'));
Copy the code

As you can see, the AppRegistry API is closer to writing RN, and the render method is the same as reactdom.render.

At this point, you can turn an existing RN page into a Web page

Next, use the AppRegistry API as an entry point to see what the React-Native Web does

React-native Web source code analysis

From three parts to analyze the source code:

  • The entry, AppRegistry API
  • API, the IMPLEMENTATION of RN API
  • Component, which is implemented on RN components

Entry point: AppRegistry API

Entry file code:

// index.web.js
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('rn_web', () => App);

AppRegistry.runApplication('rn_web', {
    rootTag: document.getElementById('react-root')});Copy the code

So what do these two apis do

AppRegistry.registerComponent

const runnables = {};
static registerComponent(appKey: string, componentProvider: ComponentProvider): string {
    runnables[appKey] = {
        getApplication: appParameters= > getApplication(componentProviderInstrumentationHook(componentProvider), appParameters ? appParameters.initialProps : emptyObject, wrapperComponentProvider && wrapperComponentProvider(appParameters)),
        run: appParameters= > renderApplication(componentProviderInstrumentationHook(componentProvider), appParameters.initialProps || emptyObject, appParameters.rootTag, wrapperComponentProvider && wrapperComponentProvider(appParameters), appParameters.callback)
    };
    return appKey;
}
Copy the code

In the example code, this method defines the runnables[‘rn_web’] object, which has two methods, getApplication and Run

AppRegistry.runApplication

static runApplication(appKey: string, appParameters: Object) :void {
    runnables[appKey].run(appParameters);
}
Copy the code

In the example code, this method is called

runnables['rn_web'].run({
    rootTag: document.getElementById('react-root')})Copy the code

The appParameters data structure is as follows:

{
    initialProps, / / the initial props
    rootTag, // root DOM node
    callback, // The callback function
}
Copy the code

renderApplication

import { render } from 'react-dom';
const renderFn = render;
function renderApplication<Props: Object> (RootComponent: ComponentType
       
        , initialProps: Props, rootTag: any, WrapperComponent? :? ComponentType<*>, callback? : (
       ) = >void) {
    renderFn(
        <AppContainer WrapperComponent={WrapperComponent} rootTag={rootTag}>
            <RootComponent {. initialProps} / >
        </AppContainer>,
        rootTag,
        callback
    );
}
Copy the code

The actual call is:

ReactDOM.render(
    <AppContainer WrapperComponent={WrapperComponent} rootTag={rootTag}>
        <App {. initialProps} / >
    </AppContainer>,
    rootTag,
    callback
);
Copy the code

AppContainer

export default class AppContainer extends Component<Props.State> {
  state = { mainKey: 1 };

  static childContextTypes = {
    rootTag: any
  };

  static propTypes = {
    WrapperComponent: any,
    children: node,
    rootTag: any.isRequired
  };

  getChildContext(): Context {
    return {
      rootTag: this.props.rootTag
    };
  }

  render() {
    const { children, WrapperComponent } = this.props;
    let innerView = (
      <View
        children={children}
        key={this.state.mainKey}
        pointerEvents="box-none"
        style={styles.appContainer}
      />
    );

    if (WrapperComponent) {
      innerView = <WrapperComponent>{innerView}</WrapperComponent>;
    }
    return (
      <View pointerEvents="box-none" style={styles.appContainer}>
        {innerView}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  appContainer: {
    flex: 1
  }
});
Copy the code

API

Take StyleSheet as an example to analyze the source code of the React-Native Web API

We all know that stylesheets used in RN are a subset of CSS, so let’s look at how the React-Native Web handles stylesheets

StyleSheet

const StyleSheet = {
  absoluteFill,
  absoluteFillObject,
  compose(style1, style2) {
    ...
  },
  create(styles) {
    ...
  },
  flatten: flattenStyle,
  hairlineWidth: 1
};
Copy the code

RN’s StyleSheet module has the following methods and constants:

1. Methods:

  • A risk setStyleAttributePreprocessor (the method)
  • create
  • flatten

2. Constants:

  • hairlineWidth
  • absoluteFill
  • absoluteFillObject

Can be found that the react – native – the web StyleSheet defines except setStyleAttributePreprocessor (risk) apply all methods and constants. In addition, the compose method, which is used in the React-Native Web component, has been added

Let’s start with the StyleSheet. Create method

StyleSheet.create
create(styles) {
  const result = {};
  Object.keys(styles).forEach(key= > {
    const id = styles[key] && ReactNativePropRegistry.register(styles[key]);
    result[key] = id;
  });
  return result;
}
Copy the code

Code is relatively simple, mainly is the traversal styles, for all styles call ReactNativePropRegistry. Register for the corresponding id, return the corresponding key – object id. Let’s start with an example:

const styles = StyleSheet.create({
  container: {
    flex: 1.justifyContent: 'center'.alignItems: 'center'.backgroundColor: '#F5FCFF',},welcome: {
    fontSize: 20.textAlign: 'center'.margin: 10,},instructions: {
    textAlign: 'center'.color: '# 333333'.marginBottom: 5,},ellipsis: {
    width: 200,}});console.log(styles);
Copy the code

Let’s see what styles are printed out?

{container: 78.welcome: 79.instructions: 80.ellipsis: 81}
Copy the code

Then look at ReactNativePropRegistry. Do the register

ReactNativePropRegistry
const emptyObject = {};
const objects = {};
const prefix = 'r';
let uniqueID = 1;

const createKey = id= > `${prefix}-${id}`;

export default class ReactNativePropRegistry {
  static register(object: Object): number {
    const id = uniqueID++;
    if(process.env.NODE_ENV ! = ='production') {
      Object.freeze(object);
    }
    const key = createKey(id);
    objects[key] = object;
    return id;
  }

  static getByID(id: number): Object {
    if(! id) {return emptyObject;
    }
    const key = createKey(id);
    const object = objects[key];
    if(! object) {return emptyObject;
    }
    returnobject; }}Copy the code

This module defines two methods: Register and getByID. Register stores a style object into a Objects object and returns the corresponding ID. GetByID gets the style object by ID

In addition to stylesheet.create, you also need to look at the stylesheet.flatten method, a flattener style, throughout the react-Native Web style transformation

flattenStyle
function getStyle(style) {
  if (typeof style === 'number') {
    return ReactNativePropRegistry.getByID(style);
  }
  return style;
}

function flattenStyle(style: ? StyleObj): ?Object {
  if(! style) {return undefined;
  }

  if (!Array.isArray(style)) {
    return getStyle(style);
  }

  const result = {};
  for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
    const computedStyle = flattenStyle(style[i]);
    if (computedStyle) {
      for (const key in computedStyle) {
        constvalue = computedStyle[key]; result[key] = value; }}}return result;
}
Copy the code

FlattenStyle method accepts the styles of parameters are being stylesheet id array, or variable, by the recursive traversal styles, call on the part of the mentioned ReactNativePropRegistry. GetByID method, by id for the style of the corresponding object, and returns.

Above, we took StyleSheet as an example to analyze the source code of RN API implemented by React-Native Web.

component

Take View component as an example to analyze the source code of the React-Native Web component

const calculateHitSlopStyle = hitSlop= > {
  const hitStyle = {};
  for (const prop in hitSlop) {
    if (hitSlop.hasOwnProperty(prop)) {
      const value = hitSlop[prop];
      hitStyle[prop] = value > 0 ? - 1 * value : 0; }}return hitStyle;
};

class View extends Component<ViewProps> {
  static displayName = 'View';

  static contextTypes = {
    isInAParentText: bool
  };

  static propTypes = ViewPropTypes;

  render() {
    const hitSlop = this.props.hitSlop;
    const supportedProps = filterSupportedProps(this.props);

    const { isInAParentText } = this.context;

    supportedProps.style = StyleSheet.compose(
      styles.initial,
      StyleSheet.compose(isInAParentText && styles.inline, this.props.style)
    );

    if (hitSlop) {
      const hitSlopStyle = calculateHitSlopStyle(hitSlop);
      const hitSlopChild = createElement('span', { style: [styles.hitSlop, hitSlopStyle] });
      supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
    }

    return createElement('div', supportedProps); }}const styles = StyleSheet.create({
  // https://github.com/facebook/css-layout#default-values
  initial: {
    alignItems: 'stretch'.borderWidth: 0.borderStyle: 'solid'.boxSizing: 'border-box'.display: 'flex'.flexDirection: 'column'.margin: 0.padding: 0.position: 'relative'.zIndex: 0.// fix flexbox bugs
    minHeight: 0.minWidth: 0
  },
  inline: {
    display: 'inline-flex'
  },
  // this zIndex-ordering positions the hitSlop above the View but behind
  // its childrenhitSlop: { ... StyleSheet.absoluteFillObject,zIndex: - 1}});export default applyLayout(applyNativeMethods(View));
Copy the code

The React View component is a simple React component.

export default applyLayout(applyNativeMethods(View));
Copy the code

ApplyNativeMethods converts native methods into corresponding DOM methods. The applyLayout method overrides the component’s lifecycle function. This part of the interested partners to learn ~

Let’s look at the Render method of the View component, which performs some manipulation on the props of the component, checks whether the props support it, handles the style, and calls the createElement method

createElement

const createElement = (component, props, ... children) = > {
  // use equivalent platform elements where possible
  let accessibilityComponent;
  if (component && component.constructor === String) {
    accessibilityComponent = AccessibilityUtil.propsToAccessibilityComponent(props);
  }
  const Component = accessibilityComponent || component;
  const domProps = createDOMProps(Component, props);
  adjustProps(domProps);
  returnReact.createElement(Component, domProps, ... children); };Copy the code

The React Element is created by calling the React. CreateElement method. Before that, the main thing to do is call the createDOMProps method to get the domProps

createDOMProps

const createDOMProps = (component, props, styleResolver) = >{... const { ... . domProps } = props;// GENERAL ACCESSIBILITY.// DISABLED.// FOCUS
  // Assume that 'link' is focusable by default (uses <a>).
  // Assume that 'button' is not (uses <div role='button'>) but must be treated as such..// STYLE
  // Resolve React Native styles to optimized browser equivalent
  const reactNativeStyle = [
    component === 'a' && resetStyles.link,
    component === 'button' && resetStyles.button,
    role === 'heading' && resetStyles.heading,
    component === 'ul' && resetStyles.list,
    role === 'button' && !disabled && resetStyles.ariaButton,
    pointerEvents && pointerEventsStyles[pointerEvents],
    providedStyle,
    placeholderTextColor && { placeholderTextColor }
  ];
  const { className, style } = styleResolver(reactNativeStyle);
  if (className && className.constructor === String) {
    domProps.className = props.className ? `${props.className} ${className}` : className;
  }
  if (style) {
    domProps.style = style;
  }

  // OTHER
  // Link security and automation test ids. return domProps; };Copy the code

The createDOMProps method is a long code, so I’m not going to paste it all in here. As you can see from a few comments, this method converts each props to the corresponding Props on the Web side.

Most of the style transformation work is in the styleResolver method, which calls the Resolve method of the ReactNativeStyleResolver instance. This method finally returns className and style, and finally assigns to domProps

styleResolver

resolve(style) {
  // fast and cachable
  // style: id
  if (typeof style === 'number') {
    this._injectRegisteredStyle(style);
    const key = createCacheKey(style);
    return this._resolveStyleIfNeeded(style, key);
  }
  // resolve a plain RN style object
  // style: style object
  if (!Array.isArray(style)) {
    return this._resolveStyleIfNeeded(style);
  }
  // flatten the style array
  // cache resolved props when all styles are registered
  // otherwise fallback to resolving
  // style: array of storage ids
  const flatArray = flattenArray(style);
  let isArrayOfNumbers = true;
  for (let i = 0; i < flatArray.length; i++) {
    const id = flatArray[i];
    if (typeofid ! = ='number') {
      isArrayOfNumbers = false;
    } else {
      this._injectRegisteredStyle(id); }}const key = isArrayOfNumbers ? createCacheKey(flatArray.join(The '-')) : null;
  return this._resolveStyleIfNeeded(flatArray, key);
}
Copy the code

Next look at _injectRegisteredStyle and _resolveStyleIfNeeded

_injectRegisteredStyle

_injectRegisteredStyle(id) {
  const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
  const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
  if (!this.injectedCache[dir][id]) {
    // Get the style object according to the id
    const style = flattenStyle(id);
    // Format the style object: sort the style attributes; Add length units; Color value processing; Specific attribute handling; Returns the formatted style object
    const domStyle = createReactDOMStyle(i18nStyle(style));
    Object.keys(domStyle).forEach(styleProp= > {
      const value = domStyle[styleProp];
      if(value ! =null) {
        // Insert the style into WebStyleSheet (domStyleElement.sheet)
        this.styleSheetManager.injectDeclaration(styleProp, value); }});// Mark this style as inserted
    this.injectedCache[dir][id] = true; }}Copy the code

Among them, the styleSheetManager. InjectDeclaration is based on the domStyleElement. Sheet to insert page style, we can see the turn out of the style of the web page:

_resolveStyleIfNeeded

The _resolveStyleIfNeeded method is called _resolveStyleIfNeeded.

_resolveStyle(style) {
  // Get the style object with the corresponding ID
  const flatStyle = flattenStyle(style);
  // Format the style object: sort the style attributes; Add length units; Color value processing; Specific attribute handling; Returns the formatted style object
  const domStyle = createReactDOMStyle(i18nStyle(flatStyle));

  const props = Object.keys(domStyle).reduce(
    (props, styleProp) = > {
      const value = domStyle[styleProp];
      if(value ! =null) {
        // Get the className for the specific style attribute and value in the WebStyleSheet
        // Styles created by StyleSheet. Create are inserted into WebStyleSheet
        const className = this.styleSheetManager.getClassName(styleProp, value);
        if (className) {
          // put this className in props. ClassList
          props.classList.push(className);
        } else {
          // Certain properties and values are not transformed by 'createReactDOMStyle' as they
          // require more complex transforms into multiple CSS rules. Here we assume that StyleManager
          // can bind these styles to a className, and prevent them becoming invalid inline-styles.
          // A single style property, if not a special property, is placed directly in props. Style
          // A single style attribute refers to a style that was not created through stylesheet.create
          if (
            styleProp === 'pointerEvents' ||
            styleProp === 'placeholderTextColor' ||
            styleProp === 'animationName'
          ) {
            const className = this.styleSheetManager.injectDeclaration(styleProp, value);
            if(className) { props.classList.push(className); }}else {
            if(! props.style) { props.style = {}; }// 4x slower renderprops.style[styleProp] = value; }}}return props;
    },
    { classList: []}); props.className = classListToString(props.classList);if (props.style) {
    props.style = prefixInlineStyles(props.style);
  }
  return props;
}
Copy the code

This method gets the className or style corresponding to all the styles and returns it in props

Above, we take View component as an example to analyze the source code of RN component implemented by React-Native Web.

After we’ve done the source code analysis, let’s see how we can make some changes based on the React-Native Web

practice

For example, the RN Text component can set numberOfLines to single or multiple line ellipses, but the React-native Web only implements single line ellipses, so we need to add multiple line ellipses as follows:

class Text extends Component< * >{... render() { ...// allow browsers to automatically infer the language writing directionotherProps.dir = dir ! = =undefined ? dir : 'auto';
    otherProps.style = [
      styles.initial,
      this.context.isInAParentText === true && styles.isInAParentText,
      style,
      selectable === false && styles.notSelectable,
      numberOfLines === 1 && styles.singleLineStyle,
      onPress && styles.pressable
    ];
    // Multiple line elisions are supported
    if (numberOfLines > 1) {
      otherProps.style.push({
        display: '-webkit-box'.WebkitBoxOrient: 'vertical'.WebkitLineClamp: numberOfLines,
        overflow: 'hidden'.textOverflow: 'ellipsis'}); }const component = isInAParentText ? 'span' : 'div';
    returncreateElement(component, otherProps); }... }Copy the code

The react-native Web source code can be used to convert the Web, and the react-native Web source code can be used to convert the Web, and the react-native Web source code can be used to convert the Web

See the sample project rn_web for more code, including source code comments and sample code

Write in the last

This is my react-native Web source code. I hope it will be helpful for those who need it

If you like my articles, go to my personal blog star ⭐️