useRef 不动时,引用它
比如一个表单获取文本框的值,可以用useState,可以用useRef。如果不涉及赋值,也就是不涉及量的变化操作,用useRef更好。
import { useState } from 'react'; import './App.css'; const App = () => { const [username, setUsername] = useState(''); return ( <> <form> <div> <input name='username' onChange={(event) => { setUsername(event.target.value); }} /> </div> <button onClick={(event) => { event.preventDefault(); console.log(username); //不建议的方式,因为每打入一个字母,就要setUserName一次 }} > GET USERNAME </button> </form> </> ); }; export default App;
import { useRef } from 'react'; import './App.css'; const App = () => { const inputRef = useRef<HTMLInputElement>(null); return ( <> <form> <div> <input name='username' ref={inputRef} /> </div> <button onClick={(event) => { event.preventDefault(); console.log(inputRef.current?.value); //更理想的获取方式,不管过程和变化,只要结果 }} > GET USERNAME </button> </form> </> ); }; export default App;
2. React.Memo 组件别乱动
我有一个页面App.tsx
,页面上就俩东西,一个是按钮自增,一个是子组件叫大列表BigList.tsx
根据react默认的渲染规则,我App渲染时,子组件也重新渲染。也就是,我自增按钮每次点击,大列表也渲染了,大浪费了,完全没必要。代码大概是这样:
import { useState } from 'react'; import BigList from './BigList'; import './App.css'; const App = () => { const [count, setCount] = useState(0); return ( <> <div className='countBlock'>{count}</div> <button onClick={() => { setCount((count) => count + 1); //每次点击按钮,我庞大的列表也会重新渲染 }} > 自增按钮 </button> <div> <BigList /> </div> </> ); }; export default App;
const BigList = () => { const listData = ['庞大的列表', '庞大的列表']; console.log('庞大的列表重新渲染了'); return ( <> <ul style={{ border: '1px solid red' }}> {listData.map((value) => ( <li>{value}</li> ))} </ul> </> ); }; export default BigList;
现在我要做的就是,点击自增按钮时,大列表不渲染。这事特别简单,把BigList用React.Memo
括一下。
import React from 'react'; const BigList = () => { const listData = ['庞大的列表', '庞大的列表']; console.log('庞大的列表重新渲染了'); return ( <> <ul style={{ border: '1px solid red' }}> {listData.map((value) => ( <li>{value}</li> ))} </ul> </> ); }; export default React.memo(BigList); //如此简单就搞定了
React.memo告诉react,我这个组件如果没有内部变化,你不要重新渲染我。
但是,现实一般没有这么简单。上面这个BigList是个连props都没有的货,它里面的列表完全自给自足,这个情况在现实中不多。万一,万一它的列表数据是从props过来的,那就完犊子了。我们把listData移动到app里面,通过props传给它。
import { useState } from 'react'; import BigList from './BigList'; import './App.css'; const App = () => { const [count, setCount] = useState(0); const listData = ['庞大的列表', '庞大的列表']; //移动到这了 return( // 其它没变省略了 <div> <BigList listData={listData} /> //通过props传过去 </div> ); // 其它没变省略了 }; export default App;
import React from 'react'; type BigListProps = { listData: string[]; }; const BigList = (props: BigListProps) => { console.log('庞大的列表重新渲染了'); return ( <> <ul style={{ border: '1px solid red' }}> {props.listData.map((value) => ( //通过props传过来的值进行渲染 <li>{value}</li> ))} </ul> </> ); }; export default React.memo(BigList);
这时,我们会发现,React.memo
无效了。原因的话,因为props毕竟是母组件传过来的,app.tsx重新渲染时,那个listData变量也重新生成了(虽然值没有变化,但数组或对象在内存中存储的地址不同),所以导致我们大列表组件内部变了,也就再次重新渲染了。
3. useMemo 变量别乱动
useMemo这个hook专门处理变量乱动的问题,简单把app.tsx里的listData改造下
const listData = useMemo(() => { return ['庞大的列表', '庞大的列表']; }, []);
成功!
但是还有一个闹心的场景,useMemo只能用在变量上,那如果母组件和子组件还有函数传递的话。。。
比如,我在它俩之间传递一个名叫ok的函数
const App = () => { ..... const ok = () => { console.log('ok'); }; return ( <> ..... <div> <BigList listData={listData} ok={ok} /> // 多传了一个ok </div> </> ); };
import React from 'react'; type BigListProps = { listData: string[]; ok: () => void; }; const BigList = (props: BigListProps) => { console.log('庞大的列表重新渲染了'); return ( <> <ul style={{ border: '1px solid red' }}> {props.listData.map((value) => ( <li>{value}</li> ))} <div> <button onClick={() => { props.ok(); //加了个按钮,专门执行传过来的ok }} > OK BUTTON </button> </div> </ul> </> ); }; export default React.memo(BigList);
再执行的话,又完了。
4. useCallback 函数别乱动
通过useCallback把app.tsx中ok函数改造下,就可以解决。
const ok = useCallback(() => { console.log('ok'); }, []);
5. useCallback vs useMemo
如果你突发其想,非要用useMemo试一下,返回一个函数,也成功
const ok = useMemo(() => { return () => { console.log('ok'); }; }, []);
这时候,不懂事的你就要问了,它俩有啥区别呢?
经过一翻查询,我觉得这个问题,只能这么说:
useMemo更关注值,或者说结果。
useCallback更关注过程,或者说函数。
非要弄个例子的话,太难了,我也在迷茫中,我觉得不要过分追求,咱们还是保持平常心,爱咋咋地。
6. 这么好的东西,使劲用?
毕竟这些也是消耗资源的,肯定不是越多越好。
如果是,消耗资源的子列表,附属的弹窗、抽屉组件,真是没必要做多余渲染的,那用下还是好的,毕竟能够提高运行效率。
如果只是简单的一段文字,还要加代码改造,不用也罢。