直接操作
有时需要直接修改某个组件,而不通过 state/props 触发整个子树的重新渲染。例如,在浏览器中使用 React 时,你有时需要直接修改一个 DOM 节点;在移动应用中的视图也是如此。setNativeProps 相当于直接在 DOM 节点上设置属性的 React Native 版本。
当频繁重新渲染造成性能瓶颈时,请使用 setNativeProps!
直接操作并不是你会经常使用的工具。通常只有在创建连续动画时才会用到它,以避免渲染组件层级和协调大量视图所带来的开销。
setNativeProps 是命令式的,并且把状态存储在原生层(DOM、UIView 等)中,而不是放在你的 React 组件内部,这会让代码更难以理解。
在使用它之前,先尝试用 setState 和 shouldComponentUpdate 来解决问题。
在 TouchableOpacity 中使用 setNativeProps
TouchableOpacity 内部使用 setNativeProps 来更新其子组件的不透明度:
const viewRef = useRef<View>();
const setOpacityTo = useCallback(value => {
// 已移除:与动画相关的代码
viewRef.current.setNativeProps({
opacity: value,
});
}, []);
这样我们就可以编写下面的代码,并且知道子组件会在点击时更新不透明度,而子组件本身并不知道这一点,也不需要对其实现做任何修改:
<TouchableOpacity onPress={handlePress}>
<View>
<Text>按我!</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>按我!</Text>
</View>
</TouchableOpacity>
);
与前面的示例相比,这种方式的计算开销更大——即使视图及其子组件的其他属性没有变化,React 仍然需要在每次不透明度变化时重新渲染组件层级。通常这种开销并不令人担忧,但在执行连续动画和响应手势时,谨慎地优化组件可以提升动画的保真度。
如果你查看 NativeMethodsMixin 中 setNativeProps 的实现,会发现它只是对 RCTUIManager.updateView 的一层封装——这与重新渲染后最终调用的函数完全相同——参见 ReactNativeBaseComponent 中的 receiveComponent。
复合组件与 setNativeProps
复合组件没有对应的原生视图,因此不能在它们上面调用 setNativeProps。考虑下面这个例子:
- TypeScript
- JavaScript
如果你运行这段代码,会立刻看到这个错误:Touchable child must either be native or forward setNativeProps to a native component。出现这个错误是因为 MyButton 并没有直接绑定到一个原生视图,因此无法设置其不透明度。你可以这样理解:如果你用 createReactClass 定义一个组件,你不会期望能直接给它设置 style 属性并让它生效——你需要把 style 属性传递给它的子组件,除非你包裹的是一个原生组件。同样地,我们将把 setNativeProps 转发给一个由原生视图承载的子组件。
将 setNativeProps 转发给子组件
由于 setNativeProps 方法存在于任何指向 View 组件的 ref 上,因此只需将自定义组件上的 ref 转发到它渲染出的某个 <View /> 组件即可。这意味着,在自定义组件上调用 setNativeProps 会产生与在被包裹的 View 组件本身上调用 setNativeProps 相同的效果。
- TypeScript
- JavaScript
现在你就可以在 TouchableOpacity 中使用 MyButton 了!
你可能已经注意到,我们使用 {...props} 将所有 props 都传递给了子视图。这样做的原因是,TouchableOpacity 实际上是一个复合组件,因此除了依赖其子组件上的 setNativeProps 之外,它还要求子组件能够处理触摸事件。为此,它会传递一些会回调到 TouchableOpacity 组件的各种 props。相比之下,TouchableHighlight 由原生视图支持,并且只要求我们实现 setNativeProps。
使用 setNativeProps 编辑 TextInput 的值
setNativeProps 的另一个非常常见的用法是编辑 TextInput 的值。TextInput 的 controlled 属性在 bufferDelay 较低且用户输入非常快时,有时会丢失字符。有些开发者更喜欢完全跳过这个属性,而是在需要时使用 setNativeProps 直接操作 TextInput 的值。例如,下面的代码演示了在你点击按钮时编辑输入框:
- TypeScript
- JavaScript
你可以使用 clear 方法来清空 TextInput,它会采用同样的方式清除当前输入文本。
避免与 render 函数冲突
如果你更新了一个也由 render 函数管理的属性,可能会导致一些不可预测且令人困惑的 bug,因为只要组件重新渲染且该属性发生变化,之前通过 setNativeProps 设置的任何值都会被完全忽略并覆盖。
setNativeProps 与 shouldComponentUpdate
通过智能地应用 shouldComponentUpdate,你可以避免在协调未变更组件子树时产生不必要的开销,甚至在性能足够好的情况下,使用 setState 代替 setNativeProps。
其他原生方法
这里描述的方法在 React Native 提供的大多数默认组件上都可用。不过请注意,它们在并非直接由原生视图支持的复合组件上是 不可用 的。通常这包括你在自己应用中定义的大多数组件。
measure(callback)
确定给定视图在屏幕上的位置、宽度和视口中的高度,并通过异步回调返回这些值。如果成功,回调将使用以下参数被调用:
- x
- y
- width
- height
- pageX
- pageY
请注意,这些测量值要等到原生端完成渲染后才能获得。如果你需要尽快获取测量值,并且不需要 pageX 和 pageY,可以考虑改用 onLayout 属性。
另外,measure() 返回的宽度和高度是组件在视口中的宽度和高度。如果你需要组件的实际大小,可以考虑改用 onLayout 属性。
measureInWindow(callback)
确定给定视图在窗口中的位置,并通过异步回调返回这些值。如果 React 根视图嵌入在另一个原生视图中,这将返回绝对坐标。如果成功,回调将使用以下参数被调用:
- x
- y
- width
- height
measureLayout(relativeToNativeComponentRef, onSuccess, onFail)
与 measure() 类似,但会相对于通过 relativeToNativeComponentRef 引用指定的某个祖先视图来测量该视图。这意味着返回的坐标是相对于祖先视图原点 x、y 的。
此方法也可以使用 relativeToNativeNode 处理器(而不是引用)来调用,但在新架构中此变体已过时。
- TypeScript
- JavaScript
focus()
请求给定输入或视图获得焦点。触发的具体行为取决于平台和视图类型。
blur()
移除输入或视图的焦点。这与 focus() 相反。