mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-27 21:07:26 +08:00
[fix] setNativeProps resolving logic
Since styles are set using both class names and inline styles, 'setNativeProps' needs an additional resolving step that accounts for the pre-existing state of RN-managed styles on the DOM node. Fix #321
This commit is contained in:
@@ -4,7 +4,7 @@ It is sometimes necessary to make changes directly to a component without using
|
|||||||
state/props to trigger a re-render of the entire subtree – in the browser, this
|
state/props to trigger a re-render of the entire subtree – in the browser, this
|
||||||
is done by directly modifying a DOM node. `setNativeProps` is the React Native
|
is done by directly modifying a DOM node. `setNativeProps` is the React Native
|
||||||
equivalent to setting properties directly on a DOM node. Use direct
|
equivalent to setting properties directly on a DOM node. Use direct
|
||||||
manipulation when frequent re-rendering creates a performance bottleneck Direct
|
manipulation when frequent re-rendering creates a performance bottleneck. Direct
|
||||||
manipulation will not be a tool that you reach for frequently.
|
manipulation will not be a tool that you reach for frequently.
|
||||||
|
|
||||||
## `setNativeProps` and `shouldComponentUpdate`
|
## `setNativeProps` and `shouldComponentUpdate`
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ const resolveProps = (reactNativeStyle) => {
|
|||||||
style: prefixInlineStyles(style)
|
style: prefixInlineStyles(style)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
||||||
console.groupCollapsed('[StyleSheet] resolving uncached styles');
|
console.groupCollapsed('[StyleSheet] resolving uncached styles');
|
||||||
console.log(
|
console.log(
|
||||||
@@ -106,6 +107,7 @@ const resolveProps = (reactNativeStyle) => {
|
|||||||
console.log('resolve => \n', props);
|
console.log('resolve => \n', props);
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
@@ -131,6 +133,7 @@ const StyleRegistry = {
|
|||||||
initialize(classNames) {
|
initialize(classNames) {
|
||||||
injectedClassNames = classNames;
|
injectedClassNames = classNames;
|
||||||
|
|
||||||
|
/*
|
||||||
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
||||||
if (global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer) {
|
if (global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer) {
|
||||||
clearInterval(global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer);
|
clearInterval(global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer);
|
||||||
@@ -142,6 +145,7 @@ const StyleRegistry = {
|
|||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
},
|
},
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
|||||||
@@ -126,17 +126,16 @@ describe('apis/UIManager', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test('add new className to existing className', () => {
|
test('supports className alias for class', () => {
|
||||||
const node = createNode();
|
const node = createNode();
|
||||||
node.className = 'existing';
|
|
||||||
const props = { className: 'extra' };
|
const props = { className: 'extra' };
|
||||||
UIManager.updateView(node, props, componentStub);
|
UIManager.updateView(node, props, componentStub);
|
||||||
expect(node.getAttribute('class')).toEqual('existing extra');
|
expect(node.getAttribute('class')).toEqual('extra');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('adds correct DOM styles to existing style', () => {
|
test('adds correct DOM styles to existing style', () => {
|
||||||
const node = createNode({ color: 'red' });
|
const node = createNode({ color: 'red' });
|
||||||
const props = { style: { marginVertical: 0, opacity: 0 } };
|
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
|
||||||
UIManager.updateView(node, props, componentStub);
|
UIManager.updateView(node, props, componentStub);
|
||||||
expect(node.getAttribute('style')).toEqual('color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;');
|
expect(node.getAttribute('style')).toEqual('color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import asap from 'asap';
|
import asap from 'asap';
|
||||||
import createReactDOMStyle from '../StyleSheet/createReactDOMStyle';
|
|
||||||
import flattenStyle from '../StyleSheet/flattenStyle';
|
|
||||||
import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
|
import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
|
||||||
import prefixInlineStyles from '../StyleSheet/prefixInlineStyles';
|
|
||||||
|
|
||||||
const _measureLayout = (node, relativeToNativeNode, callback) => {
|
const _measureLayout = (node, relativeToNativeNode, callback) => {
|
||||||
asap(() => {
|
asap(() => {
|
||||||
@@ -47,13 +44,12 @@ const UIManager = {
|
|||||||
const value = props[prop];
|
const value = props[prop];
|
||||||
switch (prop) {
|
switch (prop) {
|
||||||
case 'style': {
|
case 'style': {
|
||||||
const style = prefixInlineStyles(createReactDOMStyle(flattenStyle(value)));
|
CSSPropertyOperations.setValueForStyles(node, value, component._reactInternalInstance);
|
||||||
CSSPropertyOperations.setValueForStyles(node, style, component._reactInternalInstance);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'class':
|
case 'class':
|
||||||
case 'className': {
|
case 'className': {
|
||||||
node.classList.add(value);
|
node.setAttribute('class', value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'text':
|
case 'text':
|
||||||
|
|||||||
@@ -7,8 +7,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import findNodeHandle from '../findNodeHandle';
|
import findNodeHandle from '../findNodeHandle';
|
||||||
|
import StyleRegistry from '../../apis/StyleSheet/registry';
|
||||||
import UIManager from '../../apis/UIManager';
|
import UIManager from '../../apis/UIManager';
|
||||||
|
|
||||||
|
const REGEX_CLASSNAME_SPLIT = /\s+/;
|
||||||
|
const REGEX_STYLE_PROP = /rn-(.*):.*/;
|
||||||
|
|
||||||
|
const classNameFilter = (className) => { return className !== ''; };
|
||||||
|
const classNameToList = (className = '') => className.split(REGEX_CLASSNAME_SPLIT).filter(classNameFilter);
|
||||||
|
const getStyleProp = (className) => {
|
||||||
|
const match = className.match(REGEX_STYLE_PROP);
|
||||||
|
if (match) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const NativeMethodsMixin = {
|
const NativeMethodsMixin = {
|
||||||
/**
|
/**
|
||||||
* Removes focus from an input or view. This is the opposite of `focus()`.
|
* Removes focus from an input or view. This is the opposite of `focus()`.
|
||||||
@@ -45,7 +58,7 @@ const NativeMethodsMixin = {
|
|||||||
* - height
|
* - height
|
||||||
*
|
*
|
||||||
* Note that these measurements are not available until after the rendering
|
* Note that these measurements are not available until after the rendering
|
||||||
* has been completed in native.
|
* has been completed.
|
||||||
*/
|
*/
|
||||||
measureInWindow(callback) {
|
measureInWindow(callback) {
|
||||||
UIManager.measureInWindow(findNodeHandle(this), callback);
|
UIManager.measureInWindow(findNodeHandle(this), callback);
|
||||||
@@ -60,9 +73,49 @@ const NativeMethodsMixin = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This function sends props straight to the underlying DOM node.
|
* This function sends props straight to the underlying DOM node.
|
||||||
|
* This works as if all styles were set as inline styles. Since a DOM node
|
||||||
|
* may aleady be styled with class names and inline styles, we need to get
|
||||||
|
* the initial styles from the DOM node and merge them with incoming props.
|
||||||
*/
|
*/
|
||||||
setNativeProps(nativeProps: Object) {
|
setNativeProps(nativeProps: Object) {
|
||||||
UIManager.updateView(findNodeHandle(this), nativeProps, this);
|
// DOM state
|
||||||
|
const node = findNodeHandle(this);
|
||||||
|
const domClassList = [ ...node.classList ];
|
||||||
|
|
||||||
|
// Resolved state
|
||||||
|
const resolvedProps = StyleRegistry.resolve(nativeProps.style);
|
||||||
|
const resolvedClassList = classNameToList(resolvedProps.className);
|
||||||
|
|
||||||
|
// Merged state
|
||||||
|
const classList = [];
|
||||||
|
const style = { ...resolvedProps.style };
|
||||||
|
|
||||||
|
// The node has class names that we need to override.
|
||||||
|
// Only pass on a class name when the style is unchanged.
|
||||||
|
domClassList.forEach((c) => {
|
||||||
|
const prop = getStyleProp(c);
|
||||||
|
if (resolvedProps.className.indexOf(prop) === -1) {
|
||||||
|
classList.push(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The node has styles that we need to override.
|
||||||
|
// Remove any inline style that may collide with a new class name.
|
||||||
|
resolvedClassList.forEach((c) => {
|
||||||
|
const prop = getStyleProp(c);
|
||||||
|
classList.push(c);
|
||||||
|
style[prop] = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const className = `\n${classList.sort().join('\n')}`;
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
...nativeProps,
|
||||||
|
className,
|
||||||
|
style
|
||||||
|
};
|
||||||
|
|
||||||
|
UIManager.updateView(node, props, this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user