直接操作
有时候我们需要直接改动组件并触发局部的刷新,但不使用 state 或是 props。譬如在浏览器中使用 React 库,有时候会需要直接修改一个 DOM 节点,而在手机 App 中操作 View 时也会碰到同样的情况。在 React Native 中,setNativeProps
就是等价于直接操作 DOM 节点的方法。
什么时候使用 setNativeProps 呢?在(不得不)频繁刷新而又遇到了性能瓶颈的时候。
直接操作组件并不是应该经常使用的工具。一般来说只是用来创建连续的动画,同时避免渲染组件结构和同步太多视图变化所带来的大量开销。setNativeProps
是一个“简单粗暴”的方法,它直接在底层(DOM、UIView 等)而不是 React 组件中记录 state,这样会使代码逻辑难以理清。所以在使用这个方法之前,请尽量先尝试用setState
和shouldComponentUpdate方法来解决问题。
setNativeProps 与 TouchableOpacity
TouchableOpacity这个组件就在内部使用了setNativeProps
方法来更新其子组件的透明度:
const viewRef = useRef<View>();
const setOpacityTo = useCallback(value => {
// Redacted: animation related code
viewRef.current.setNativeProps({
opacity: value,
});
}, []);
由此我们可以写出下面这样的代码:子组件可以响应点击事件,更改自己的透明度。而子组件自身并不需要处理这件事情,也不需要在实现中做任何修改。
<TouchableOpacity onPress={this._handlePress}>
<View style={styles.button}>
<Text>Press me!</Text>
</View>
</TouchableOpacity>
如果不使用setNativeProps
来实现这一需求,那么一种可能的办法是把透明值保存到 state 中,然后在onPress
事件触发时更新这个值:
const [buttonOpacity, setButtonOpacity] = useState(1);
return (
<TouchableOpacity
onPressIn={() => setButtonOpacity(0.5)}
onPressOut={() => setButtonOpacity(1)}>
<View style={{opacity: buttonOpacity}}>
<Text>Press me!</Text>
</View>
</TouchableOpacity>
);
比起之前的例子,这一做法会消耗大量的计算 —— 每一次透明值变更的时候 React 都要重新渲染组件结构,即便视图的其他属性和子组件并没有变化。一般来说这一开销也不足为虑,但当执行连续的动画以及响应用户手势的时候,只有正确地优化组件才能提高动画的流畅度。
如果你看过NativeMethodsMixin中的setNativeProps
方法的实现,你就会发现它实际是对RCTUIManager.updateView
的封装 —— 而这正是重渲染所触发的函数调用,具体可以参看ReactNativeBaseComponent.js 中的 receiveComponent.
复合组件与 setNativeProps
复合组件并不是单纯的由一个原生视图构成 ,所以你不能对其直接使用setNativeProps
。比如下面这个例子:
跑这个例子会马上看到一行报错: Touchable child must either be native or forward setNativeProps to a native component
。这是因为MyButton
并非直接由原生视图构成,而我们只能给原生视图设置透明值。你可以尝试这样去理解:如果你通过React.createClass
方法自定义了一个组件,直接给它设置样式 prop 是不会生效的,你得把样式 props 层层向下传递给子组件,直到子组件是一个能够直接定义样式的原生组件。同理,我们也需要把setNativeProps
传递给由原生组件封装的子组件。