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