我们首先来讲useMemo的用法
useMemo
首先,说起这个 我们肯定要知道 在class的声明组件中 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)
使用shouldComponentUpdate()以让React知道当前状态或属性的改变是否不影响组件的输出,默认返回ture,返回false时不会重写render,而且该方法并不会在初始化渲染或当使用**forceUpdate()**时被调用,我们要做的只是这样:
shouldComponentUpdate(nextProps, nextState) {
return nextState.someData !== this.state.someData
}
当然上面的知识 class 中的api 控制方法,
相信写过React的都知道 智能组件 和 傻瓜组件的两个名词吧,
没错,傻瓜组件就是 我们class中的 React.PureComponent
React.PureComponent通过props和state的浅对比来实现 shouldComponentUpate()。所以我们只需要往组件中传入需要显示在页面上的值就可以了。( React.memo )
useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数
React.memo的用法
import React, { useState, } from 'react';
import Child1 from './Child1';
import Child2 from './Child2 ';
export default (props = {}) => {
const [step, setStep] = useState(0);
const [val, setValue] = useState('');
const handleSetStep = () => {
setStep(step + 1);
}
const handleChange = (e) => {
setValue(e.target.value);
}
return (
<div>
<button onClick={handleSetStep}>step is : {step} </button>
<input onChange={(e) => handleChange(e)} />
<hr />
<Child1 step={step} count={count} />
</div>
);
}
这里,只要是我的input 输入发生改变,就会触发react的set state 导致re-render 方法发生渲染,就会导致Child1 发生渲染,倒是不必要的性能浪费, 我只想要step 改变才触发子组件,所以用法就是
import React, { useState } from 'react';
import Child1 from './Child1';
import Child2 from './Child2 ';
export default (props = {}) => {
const [step, setStep] = useState(0);
const [val, setValue] = useState('');
const handleSetStep = () => {
setStep(step + 1);
}
const handleChange = (e) => {
setValue(e.target.value);
}
// 改变之后
const ChildMedo = React.memo(() => {
return <Child1 step={step} count={count} />
});
return (
<div>
<button onClick={handleSetStep}>step is : {step} </button>
<input onChange={(e) => handleChange(e)} />
<hr />
<ChildMedo />
</div>
);
}
注意事项
不要坑了子组件
我们做一个点击累加的按钮作为父组件,那么父组件每次点击后都会刷新:
function App() {
const [count, forceUpdate] = useState(0);
const schema = { b: 1 };
return (
<div>
<Child schema={schema} />
<div onClick={() => forceUpdate(count + 1)}>Count {count}</div>
</div>
);
}
另外我们将 schema = { b: 1 } 传递给子组件,这个就是埋的一个大坑。
子组件的代码如下:
const Child = memo(props => {
useEffect(() => {
console.log("schema", props.schema);
}, [props.schema]);
return <div>Child</div>;
});
只要父级 props.schema 变化就会打印日志。结果自然是,父组件每次刷新,子组件都会打印日志,也就是 子组件 [props.schema] 完全失效了,因为引用一直在变化。
其实 子组件关心的是值,而不是引用,所以一种解法是改写子组件的依赖:
const Child = memo(props => {
useEffect(() => {
console.log("schema", props.schema);
}, [JSON.stringify(props.schema)]);
return <div>Child</div>;
});
当然React.memo() 还有第二个参数,来判断是否进行页面的刷新 React官网链接
useMemo的用法
export default function useMemoDemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
// 使用useMemo的话 当input的值改变了,func 还是拿到之前的缓存的值,这样子就节省了性能的再次计算的开销
const func= useMemo(() => {
let result = Math.random() * count;
return result;
}, [count]);
return <div>
<h4>{count}-{func}</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
useCallback 的用法
function Counter() {
const [count, setCount] = useState(0);
const getCount= useCallback(() => {
return "count=" + count;
}, [count]);
useEffect(() => {
getCount();
}, [getCount]);
return <h1>{count}</h1>;
}
可以看到,useCallback 也有第二个参数 - 依赖项,我们将 getCount函数的依赖项通过 useCallback 打包到新的 getCount函数中,那么 useEffect 就只需要依赖 getCount这个函数,就实现了对 count 的间接依赖。
我们来对比一下 React的useCallback 和 class中的 componentDidUpdate
class Parent extends Component {
state = {
count: 0,
step: 0
};
fetchData = () => {
const url =
"https://count=" + this.state.count + "&step=" + this.state.step;
};
render() {
return <Child fetchData={this.fetchData} count={count} step={step} />;
}
}
class Child extends Component {
state = {
data: null
};
componentDidMount() {
this.props.fetchData();
}
componentDidUpdate(prevProps) {
if (
this.props.count !== prevProps.count ||
this.props.step !== prevProps.step
) {
this.props.fetchData();
}
}
render() {
// ...
}
}
上面的代码经常用 Class Component 的人应该很熟悉,因此在 componentDidUpdate 时,判断这两个参数count step 发生了变化就触发重新取数。
如果父级函数 fetchData 不是我写的,在不读源码的情况下,我怎么知道它依赖了 props.count 与 props.step 呢?更严重的是,如果某一天 fetchData 多依赖了 params 这个参数,下游函数将需要全部在 componentDidUpdate 覆盖到这个逻辑,否则 params 变化时将不会重新取数。可以想象,这种方式维护成本巨大,甚至可以说几乎无法维护。
换成 Function Component 的思维吧!试着用上刚才提到的 useCallback 解决问题:
function Parent() {
const [ count, setCount ] = useState(0);
const [ step, setStep ] = useState(0);
const fetchData = useCallback(() => {
const url =
"https://count=" + this.state.count + "&step=" + this.state.step;
}, [count, step])
return (
<Child fetchData={fetchData} />
)
}
function Child(props) {
useEffect(() => {
props.fetchData()
}, [props.fetchData])
return (
// ...
)
}
可以看出来,当 fetchData 的依赖变化后,按下保存键,eslint-plugin-react-hooks 会自动补上更新后的依赖,子组件只需要关心传递过来的fetchData就可以了,至于这个函数依赖了什么,已经封装在 useCallback 后打包透传下来了。
还有 useCallback 如果想一直获取最新的值,下面的这个方法,将会导致 callback 一直变化,就会很影响性能
import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
const [val, setVal] = useState('');
const callback = useCallback(() => {
return "http://val="+val;
}, [val]);
return <div>
<Child callback={callback}/>
<div>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
function Child({ callback }) {
const [val, setVal] = useState(() => callback());
useEffect(() => {
setVal(callback());
}, [callback]);
return <div>
{val}
</div>
}
我们可以用过useRef来解决
import React, { useState, useCallback, useEffect,useRef } from 'react';
function Parent() {
const [val, setVal] = useState('');
const textRef = useRef();
useLayoutEffect(() => {
//这个是等待 dom发生改变后 把值赋值给 **textRef**
textRef.current = val;
});
// 之所以传入 textRef 这个的作用是 textRef 是不会变化的,但是子组件仍然可以拿到最新的值
// 节约了性能的开销
const callback = useCallback(() => {
const currentText = textRef.current;
console.log(currentText);
}, [textRef]);
return <div>
<Child callback={callback}/>
<div>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
function Child({ callback }) {
const [val, setVal] = useState(() => callback());
useEffect(() => {
setVal(callback());
}, [callback]);
return <div>
{val}
</div>
}
useCallback的依赖是只比较值的, 如果是对象, 就是只比较引用
而textRef是一直存在不会销毁的跨生命周期对象, 引用一直不变, 相当于, useCallback的依赖为[]
为什么只比较引用呢, 因为useCallback/useEffect的依赖里基本都该是useState的返回值, 而每次调用setState都会赋给state一个全新的值, 如果是对象, 引用就变了, 只需要比较值(对象的话是地址)就知道依赖有没有变化
如果有任何问题 欢迎评论指出,谢谢