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 ⭐️