mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +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
|
||||
is done by directly modifying a DOM node. `setNativeProps` is the React Native
|
||||
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.
|
||||
|
||||
## `setNativeProps` and `shouldComponentUpdate`
|
||||
|
||||
@@ -95,6 +95,7 @@ const resolveProps = (reactNativeStyle) => {
|
||||
style: prefixInlineStyles(style)
|
||||
};
|
||||
|
||||
/*
|
||||
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
||||
console.groupCollapsed('[StyleSheet] resolving uncached styles');
|
||||
console.log(
|
||||
@@ -106,6 +107,7 @@ const resolveProps = (reactNativeStyle) => {
|
||||
console.log('resolve => \n', props);
|
||||
console.groupEnd();
|
||||
}
|
||||
*/
|
||||
|
||||
return props;
|
||||
};
|
||||
@@ -131,6 +133,7 @@ const StyleRegistry = {
|
||||
initialize(classNames) {
|
||||
injectedClassNames = classNames;
|
||||
|
||||
/*
|
||||
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
||||
if (global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer) {
|
||||
clearInterval(global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer);
|
||||
@@ -142,6 +145,7 @@ const StyleRegistry = {
|
||||
console.groupEnd();
|
||||
}, 30000);
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
reset() {
|
||||
|
||||
@@ -126,17 +126,16 @@ describe('apis/UIManager', () => {
|
||||
}
|
||||
};
|
||||
|
||||
test('add new className to existing className', () => {
|
||||
test('supports className alias for class', () => {
|
||||
const node = createNode();
|
||||
node.className = 'existing';
|
||||
const props = { className: 'extra' };
|
||||
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', () => {
|
||||
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);
|
||||
expect(node.getAttribute('style')).toEqual('color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;');
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import asap from 'asap';
|
||||
import createReactDOMStyle from '../StyleSheet/createReactDOMStyle';
|
||||
import flattenStyle from '../StyleSheet/flattenStyle';
|
||||
import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
|
||||
import prefixInlineStyles from '../StyleSheet/prefixInlineStyles';
|
||||
|
||||
const _measureLayout = (node, relativeToNativeNode, callback) => {
|
||||
asap(() => {
|
||||
@@ -47,13 +44,12 @@ const UIManager = {
|
||||
const value = props[prop];
|
||||
switch (prop) {
|
||||
case 'style': {
|
||||
const style = prefixInlineStyles(createReactDOMStyle(flattenStyle(value)));
|
||||
CSSPropertyOperations.setValueForStyles(node, style, component._reactInternalInstance);
|
||||
CSSPropertyOperations.setValueForStyles(node, value, component._reactInternalInstance);
|
||||
break;
|
||||
}
|
||||
case 'class':
|
||||
case 'className': {
|
||||
node.classList.add(value);
|
||||
node.setAttribute('class', value);
|
||||
break;
|
||||
}
|
||||
case 'text':
|
||||
|
||||
@@ -7,8 +7,21 @@
|
||||
*/
|
||||
|
||||
import findNodeHandle from '../findNodeHandle';
|
||||
import StyleRegistry from '../../apis/StyleSheet/registry';
|
||||
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 = {
|
||||
/**
|
||||
* Removes focus from an input or view. This is the opposite of `focus()`.
|
||||
@@ -45,7 +58,7 @@ const NativeMethodsMixin = {
|
||||
* - height
|
||||
*
|
||||
* Note that these measurements are not available until after the rendering
|
||||
* has been completed in native.
|
||||
* has been completed.
|
||||
*/
|
||||
measureInWindow(callback) {
|
||||
UIManager.measureInWindow(findNodeHandle(this), callback);
|
||||
@@ -60,9 +73,49 @@ const NativeMethodsMixin = {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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