useState 与 useRef

2023/11/26
共 990 字
约 4 分钟
归档: 技术
标签: React

探讨一下 useState 与 useRef,useMemo 和 useCallback


开门见山

现有如下 react native 代码,这是一个很常见的 todo list

import React, {useState, memo, useRef} from 'react';

import {
    StyleSheet,
    FlatList,
    Button,
    Text,
    View,
    Modal,
    TextInput,
    TouchableOpacity,
} from 'react-native';

function Home() {
    const initData = [];
    for (let i = 1; i <= 10; i++) {
        initData.push({name: 'jack' + i, id: i.toString()});
    }
    const [data, setData] = useState(initData);
    const [input, setInput] = useState('');

    const renderItem = ({item}) => {
        console.log('renderItem');
        return (
            <Text style={styles.text}>{item.name}</Text>
        );
    };

    const addTodo = () => {
        setInput('');
        setData([{name: input, id: Math.random()}, ...data]);
    };

    const keyExtractor = (item, index) => {
        return item.id;
    };

    return (
        <View>
            <TextInput
                value={input}
                onChangeText={setInput}
            />
            <Button title={'新增'} onPress={addTodo}></Button>
            <FlatList
                showsVerticalScrollIndicator={false}
                keyExtractor={keyExtractor}
                data={data}
                renderItem={renderItem}
            />
        </View>
    );
}

const styles = StyleSheet.create({
    text: {
        fontSize: 20,
        padding: 10,
        margin: 20,
        backgroundColor: 'lavender',
        textAlign: 'center',
    },

});

export default Home;

可以发现,每次在 TextInput 输入一个字母,控制台都会打印10次 renderItem,意味着每次输入,都会触发整个 Flatlist 的重新渲染。

因为 TextInput 的值被保存在 Home 组件的状态中,每次输入框的值改变时,都会触发 setInput 函数,进而更新 input 的状态,导致 Home 组件重新渲染。
FlatList 作为 Home 的一个子组件,也会被重新渲染。

用 useRef 代替 useState

useState 是一个用于管理组件状态的 Hook。当使用 useState 更新状态时,React 会自动重新渲染组件以反映新的状态值。

useRef 则是一个用于存储可变值的 Hook,这些值在组件的重新渲染之间保持不变。
useRef 在其值发生变化时不会触发组件的重新渲染。useRef 返回一个可变的对象,其 .current 属性可以用于获取或设置值

在这个例子中,在 textInput 输入文字时,就非常适合用 useRef。这应该是最合理、最省力的方法。

代码更改如下:

// const [input, setInput] = useState('');
const inputRef = useRef(null);

addTodo 函数,获取输入值与清空输入框的方法发生变化

const addTodo = () => {
    setData([{name: inputRef.current.value, id: Math.random()}, ...data]);
    inputRef.current.clear();
};

视图绑定,使用 ref

<TextInput
    ref={inputRef}
    onChangeText={t => (inputRef.current.value = t)}
/>

抽取 textInput 与 Button

上边提到,Home 组件会重新渲染,是因为 TextInput 的值被保存在 Home 组件的状态中。那么我们把 TextInput 抽取出来,在它内部维护状态,就不会影响 Home。

这个方法的缺点是,需要把 addTodo 函数传递到 MyInput,在业务不复杂的情况,这样操作还是可以的。

代码如下

function MyInput({addTodo}) {
    const [input, setInput] = useState('');

    return (
        <View>
            <TextInput
                value={input}
                onChangeText={setInput}
            />
            <Button title={'新增'} onPress={()=>{
                addTodo(input);
                setInput('');
            }}></Button>
        </View>
    );
}

function Home() {
    const initData = [];
    for (let i = 1; i <= 10; i++) {
        initData.push({name: 'jack' + i, id: i.toString()});
    }
    const [data, setData] = useState(initData);

    const renderItem = ({item}) => {
        console.log('renderItem');
        return (
            <Text style={styles.text}>{item.name}</Text>
        );
    };

    const addTodo = (input) => {
        setData([{name: input, id: Math.random()}, ...data]);
    };

    const keyExtractor = (item, index) => {
        return item.id;
    };

    return (
        <View>
            <MyInput addTodo={addTodo}/>
            <FlatList
                showsVerticalScrollIndicator={false}
                keyExtractor={keyExtractor}
                data={data}
                renderItem={renderItem}
            />
        </View>
    );
}

useCallback

以上方法,都是从输入框的入手,阻止他触发 Home 的更新,但有时候不得不更新 Home,这时就可以从 flatList 入手,阻止 flatList 重新渲染。用 useCallback 钩子函数包装 renderItem 和 keyExtractor,以确保它们只在 data 状态改变时重新计算。

const renderItem = useCallback(({item}) => {
    console.log('renderItem');
    return (
        <Text style={styles.text}>{item.name}</Text>
    );
}, []);

const keyExtractor = useCallback((item, index) => {
    return item.id;
}, []);

React.memo

把 flatList 组件提取到一个组件(MyText),并用 React.memo 进行优化。虽然依旧会打印 renderItem,但 MyText 里的打印没有执行。

MyText 与 Home 平级

const MyText = memo(({item}) => {
    console.log('MyText');
    return (
        <Text style={styles.text}>{item.name}</Text>
    );
});
const renderItem = ({item}) => {
    console.log('renderItem');
    return (
        <MyText item={item}/>
    );
};

留言

本站已运行
© 2024 Jack  由 Hexo 驱动
复制成功