当遇到一个需求, 每次渲染一个页面时, 如果数据源没有发生变化, 实际上页面是不用重新渲染的, 但是在RN
中, 有没有一个方法是可以去满足这个需求的呢?
在函数式组件中:
需要用到React.memo
以下是原始的代码:
export default withFloatingButton(
(props: Props) => {
console.log('render...')
const theme = useContext(ThemeContext)
const styles = theme === 'dark' ? darkStyles : lightStyles
return (
<View style={styles.avarta}>
<Image
style={styles.img}
source={{uri: `${props.info.avarta}`}}
>
</Image>
<Text style={styles.titleTxt}>
{`${props.info.name}`}
</Text>
<View style={styles.descContent}>
<Text style={styles.descTxt}>
{`${props.info.desc}`}
</Text>
</View>
</View>
)
}
)
但这样写的话, 如果props
的值重新设置的话, 这个组件就会重新渲染一遍, 但是, 重新设置的props
的值事实上并不一定是变化的. 那这时候重新渲染就没有意义了, 如果这时候用React.memo包一层的话, 情况就会不同:
export default React.memo(
withFloatingButton(
(props: Props) => {
console.log('render...')
const theme = useContext(ThemeContext)
const styles = theme === 'dark' ? darkStyles : lightStyles
return (
<View style={styles.avarta}>
<Image
style={styles.img}
source={{uri: `${props.info.avarta}`}}
>
</Image>
<Text style={styles.titleTxt}>
{`${props.info.name}`}
</Text>
<View style={styles.descContent}>
<Text style={styles.descTxt}>
{`${props.info.desc}`}
</Text>
</View>
</View>
)
}
)
, (preProps: Props, nextProps: Props) => {
return JSON.stringify(preProps.info) === JSON.stringify(nextProps.info)
})
这段代码的意思是, 将原先的整个函数, 作为参数传进入React.memo的第一个参数, 那第二个参数也是一个函数:
(preProps: any, nextProps: any) => {
return xxx /// bool类型
}
这个函数的意思是将原先的preProps
和新的nextProps
进行比较, 来决定是否渲染, return
的这个返回值, 如果是true
(表示数据一样), 则不重新渲染, 如果是false
(表示数据不同), 需要重新渲染.
那么在类组件中有没有类似的方法呢?
也是有的
shouldComponentUpdate(nextProps: Readonly<Props>): boolean
需要去实现这个方法, 在这个方法中, 有点类似于React.memo
的第二个参数, 需要是当前的this.props
和新的nextProps
进行比较:
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
return JSON.stringify(nextProps.info) !== JSON.stringify(this.props.info)
}
区别在于, 当两者不相同时, 则返回true
, 表示需要重新渲染. 当两者相同时, 则返回false
, 表示不需要重新渲染. 这是跟React.memo
刚好相反的地方.
好了, 现在来聊一下如何使用useMemo
假设现在有这样一个场景

需要算出列表的某一项的总计, 那么事实上, 这个列表如何展示, 其实跟这个计算并没有什么关系, 比如, 列表也可以这样展示

但是, 如果常规的写法的话, 就会将数值再重新计算一遍, 那这个其实是很没有必要的, 试想一下, 假设这个列表有几千条数据呢? 所以, 我们需要将这个列表的数据缓存起来, 这个就用到了
useMemo
使用前:
const calculateAmount = () => {
return data.map((item) => item.amount).reduce((pre, cur) => pre + cur)
}
使用后:
// useMemo
const calculateAmount = useMemo(
() => {
return data.map((item) => item.amount).reduce((pre, cur) => pre + cur)
}, [data])
但其实上面的还不够完善, 我们仅仅只是数据需要缓存吗? 其实整个底部合计的view
都是可以缓存的. 因为当数据没有发生变化时, 整个view
都是不变的.
那么就可以写成这样:
// useMemo
const totalAmountView = useMemo(
() => {
const totalAmount = data.map((item) => item.amount).reduce((pre, cur) => pre + cur)
return (
<View style={styles.bottomView}>
<Text style={styles.titleTxt}>
{`${totalAmount}`}
</Text>
<Text style={styles.titleTxt}>
{`费用总计: `}
</Text>
</View>
)
}
, [data])
最后我们来聊一下useCallback
比如在上面的列表中, 其实cell
的点击事件是并不需要每次都创建一遍的. 那么如何缓存这些点击事件的函数呢, 这时候就要用到useCallback
了.
const didClickCell = useCallback(
(item: UserData, index: number) => {
console.log(`点击了第${index + 1}个cell, 名称是${item.name}`)
}
, [])
这样, 在onPress
属性中就可以这样写:
onPress={() => {
didClickCell(item, index)
}}
那可以直接在属性中写一个函数吗? 也是可以的. 这时, useCallback
就要改一下, 改成一个返回值是一个函数的写法:
const didClickCellNew = useCallback(
(item: UserData, index: number) => () => {
console.log(`点击了第${index + 1}个cell, 名称是${item.name}`)
}
, [])
这样, 在填写onPress
属性时, 就可以直接写函数了:
onPress={ didClickCellNew(item, index) }
网友评论