hooks概览
-
单个组件数据管理:useState & useReducer
-
变量/方法的缓存:useMemo & useCallback
-
事件触发:useEffect & useLayoutEffect
-
避免状态改变/触发rerender的数据存储:useRef
-
通过ref自定义并暴露方法:useImperativeHandle & forwardRef
-
跨组建数据状态管理:useContext(配合createContext)
-
react 18 rn 0.69新的hooks:useDeferredValue & useTransition ....
hooks使用demo&介绍
1.useState & useReducer
创建组件数据的hook
- 每次调用改变函数会重新走整个组件方法
const [test, setTest] = useState(0);
console.warn(test);
return (
<View style={{ marginTop: 100 }}>
<BlButton
title="test"
onPress={() => {
setTest(current => current + 1);
}}
/>
</View>
);
- useReducer的基础用法
type myAction =
| {
type: 'ADD';
payload: {
value: string;
index: number;
};
}
| {
type: 'DELETE';
payload: {
index: number;
};
}
| {
type: 'INIT';
payload: {
data: string[];
};
};
const [test, testDispatch] = useReducer((prevState: string[], action: myAction) => {
switch (action.type) {
case 'INIT':
return prevState;
case 'ADD':
prevState.splice(action.payload.index, 0, action.payload.value);
return [...prevState];
case 'DELETE':
prevState.splice(action.payload.index, 1);
return [...prevState];
}
}, []);
console.warn(test);
return (
<View style={{ marginTop: 100 }}>
<BlButton
title="test"
onPress={() => {
testDispatch({ type: 'ADD', payload: { value: '1', index: 0 } });
}}
/>
</View>
);
const [test, testAppend] = useReducer(
(prevState: string[], data: string[] | undefined) => (data ? [...prevState, ...data] : []),
[],
);
console.warn(test);
return (
<View style={{ marginTop: 100 }}>
<BlButton
title="test"
onPress={() => {
testAppend(['1', '2']);
}}
/>
</View>
);
- 在useEffect/自定义函数会自动合并,在setTimeout/fetch等异步方法中会调用多次
const [test, setTest] = useState(0);
console.warn(test);
return (
<View style={{ marginTop: 100 }}>
<BlButton
title="test"
onPress={() => {
setTest(current => current + 1);
setTest(current => current + 1);
}}
/>
</View>
);
const [test, setTest] = useState(0);
console.warn(test);
return (
<View style={{ marginTop: 100 }}>
<BlButton
title="test"
onPress={() => {
setTimeout(() => {
setTest(current => current + 1);
setTest(current => current + 1);
}, 0);
}}
/>
</View>
);
会多次setState的页面如果复杂计算没有使用hooks缓存,将会有性能损耗
const [test, setTest] = useState('');
console.time('time');
Array(100000).map((current, index) => Math.pow(index, 2));
console.timeEnd('time');
return (
<View style={{ marginTop: 100 }}>
<BlInput style={{ backgroundColor: 'red' }} value={test} onChangeText={setTest} />
</View>
);
2. useMemo & useCallback
数据/方法的缓存
- useCallback是useMemo的封装
const test1 = useMemo(() => () => {}, []);
const test2 = useCallback(() => {}, []);
- 加了缓存,依赖里的内容没有变动,那么内容不会重新加载
const [test, setTest] = useState('');
console.time('time');
const data = useMemo(() => Array(100000).map((current, index) => Math.pow(index, 2)), []);
const data1 = useMemo(() => Array(100000).map((current, index) => Math.pow(index, 2)), []);
const data2 = useMemo(() => Array(100000).map((current, index) => Math.pow(index, 2)), []);
const data3 = useMemo(() => Array(100000).map((current, index) => Math.pow(index, 2)), []);
console.timeEnd('time');
return (
<View style={{ marginTop: 100 }}>
<BlInput style={{ backgroundColor: 'red' }} value={test} onChangeText={setTest} />
</View>
);
- 依赖内部为浅比较,对象地址变化会导致重复加载
const myData = { content: '一段文本' };
const [test, setTest] = useState('');
console.time('time');
const data = useMemo(() => {
console.warn(myData);
return Array(100000).map((current, index) => Math.pow(index, 2));
}, [myData]);
console.timeEnd('time');
return (
<View style={{ marginTop: 100 }}>
<BlInput style={{ backgroundColor: 'red' }} value={test} onChangeText={setTest} />
</View>
);
3.useEffect & useLayoutEffect
useLayoutEffect会在页面渲染前触发,一般不使用,多数用于navigation.setOptions()方法,设置同一页面的动态headerLeft / title / headerRight 等
- 所有hooks的第二个数组参数为浅比较
const [test, setTest] = useState({ a: 'test' });
useEffect(() => {
console.warn(test.a);
}, [test]);
useEffect(() => {
setTest(current => {
current.a = 'test1';
return current;
});
}, []);
- 对于类似搜索的场景,无需手动调用方法
const [searchText, setSearchText] = useState('');
const timer = useRef();
const update = useCallback(() => {
timer.current && clearTimeout(timer.current);
timer.current = setTimeout(() => {
console.warn(searchText);
}, 1000);
}, [searchText]);
useEffect(() => {
update();
}, [update]);
- 针对上面场景,可以换种写法减少一次函数绑定
const [searchText, setSearchText] = useState('');
const timer = useRef();
const update = useCallback((value?: string) => {
timer.current && clearTimeout(timer.current);
timer.current = setTimeout(() => {
console.warn(value);
}, 1000);
}, []);
useEffect(() => {
update(searchText);
}, [update, searchText]);
4.useRef
在组件首次加载后,不再初始化,改变内容不会触发re-render。也可作为组件的标识来调用组件内部方法
- 比如在提交表单时,可以使用ref来避免re-render
const [data, setData] = useState('');
const submit = useCallback(() => {
console.warn(data);
}, [data]);
return (
<View style={{ marginTop: 100 }}>
<BlInput style={{ backgroundColor: 'red' }} onChangeText={setData} />
<BlButton title="test" onPress={submit} />
</View>
);
改进:
const dataRef = useRef('');
const changeData = useCallback((value: string) => {
dataRef.current = value;
}, []);
const submit = useCallback(() => {
console.warn(dataRef.current);
}, []);
return (
<View style={{ marginTop: 100 }}>
<BlInput style={{ backgroundColor: 'red' }} onChangeText={changeData} />
<BlButton title="test" onPress={submit} />
</View>
);
5. useImperativeHandle & forwardRef
暴露子组件内部方法,在外部可以通过useRef调用
const InnerComponent = forwardRef(
memo((props, ref) => {
const toast = useCallback(() => {
console.warn('toast');
}, []);
useImperativeHandle(ref, () => ({ toast }));
return <View style={{ width: 200, height: 200, backgroundColor: 'red' }} />;
}),
);
const innerRef = useRef();
const onPress = useCallback(() => {
innerRef.current?.toast();
}, []);
return (
<TouchableOpacity onPress={onPress}>
<InnerComponment ref={innerRef} />
</TouchableOpacity>
);
6.useContext
需要配合createContext使用,跨组建数据传递。优点是后退组件销毁后不占内存;每个dispatch各自独立,分多个对象存储,数据层面有一定安全性。
const [test, setText] = useState('');
const TestContext = createContext();
<TextContent.Provider>
<InnerComponent />
</TextContent.Provider>
const InnerComponent = () => {
const {test, setTest} = useContext(TestContext);
console.warn(test);
return (
<View style={{ marginTop: 100 }}>
<BlInput style={{ backgroundColor: 'red' }} onChangeText={setTest} />
</View>
);
}
作者:@刘力瑞 黑湖科技移动开发工程师