React Hooks 编程(二)

  • useEffect

  • useCallback

  • useMemo

这里实战的例子来源于w3cschool网站:
https://www.w3schools.com/REACT/react_usememo.asp

useEffect

useEffect 主要用来做页面的初始化渲染,React中会在页面结构渲染完成后执行useEffect中的内容,所以可以在useEffect中执行一些初始化的操作:包括日志初始化、网络fetch数据初始化等,来看下面的例子:

function Timer() {
const [count, setCount] = useState(0);


useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
console.log(count);
}, 1000);
});


return <h1>I've rendered {count} times!</h1>;
}

这里直接使用了默认的useEffect函数,所以每次渲染页面,就会触发useEffect中逻辑的执行,而这里setCount其实就是触发页面重新渲染的一次操作,因此会不断执行useEffect中的逻辑,输出结果如下:

但是很多时候只是想在页面第一次初始化的时候才执行useEffect中初始化逻辑,那就加入useEffect函数的第二个参数:



function Timer1() {
const [count, setCount] = useState(0);


useEffect(() => {
setTimeout(() => {
console.log(count+1);
setCount((count) => count + 1);
}, 1000);
}, []); // <- add empty brackets here


return <h1>I've rendered {count} times!</h1>;
}

加入参数[]后,只会在第一次渲染页面的时候,才会执行useEffect中的内容,我们可以刷新下页面,看到结果:

如果要控制useEffect根据我们自己设置的条件来变化,比如state状态发生变化后再触发执行useEffect中逻辑,那就可以在第二个参数中传入状态变量:

function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);


useEffect(() => {
console.log(count);
setCalculation(() => count * 2);
}, [count]); // <- add the count variable here


return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<p>Calculation: {calculation}</p>
</>
);
}

这时候传入的是count变量,当我们点击按钮后,就会触发count变量的变化,然后触发useEffect中代码的执行:

useCallback主要为了做性能优化,缓存一个函数,避免React渲染页面的时候,每次都生成一个新的函数对象。

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);


const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};


return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};


export default App;

const Todos = ({ todos, addTodo }) => {
console.log("child render");
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</>
);
};


export default memo(Todos);

这里新建了两个组件,父组件App,子组件Todos,可以看到在点击父组件的button按钮后,会触发increment函数,从而触发useState中count变量的执行,这时候同时触发了父组件和子组件同时渲染,是因为在父组件渲染的时候,会生成新的addTodo函数对象,这个函数对象是传入子组件中的,因此每次渲染生成新的函数对象addTodo都会引起了子组件的渲染,结果如下:

const addTodo = useCallback(() => {
setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

addTodo封装了一层useCallback后,子组件就不会每次都随着父组件而更新了:

useMemo

useMemo主要是为了做性能优化,当前端页面在渲染过程中需要一些耗时的渲染逻辑或者计算逻辑的时候,可以缓存计算的结果,为了避免随着页面的渲染而重新触发耗时逻辑的运行:

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = expensiveCalculation(count);


const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};


return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};


const expensiveCalculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};

上面的代码中用expensiveCalculation来模拟一个耗时的计算逻辑,而这段逻辑假设只跟传入的num参数有关,但是这时候只要触发addTodo函数的运行,每次都会加载expensiveCalculation函数的运行,结果如下:

所以这时候可以考虑对expensiveCalculation进行一个返回值的缓存:

 const calculation = useMemo(() => expensiveCalculation(count), [count]);

expensiveCalculation用useMemo封装后,除非count变化,这个函数不会随着页面的渲染而重新运行,这样就减少了不必要的耗时逻辑,提升了性能,结果如下:

可以看到做了优化后,耗时逻辑除了第一次会因为页面的渲染触发执行之外,后面再触发addTodo函数后,不会引发重新计算的。

以上主要通过一些简单的测试来记录useEffect、useCallabck和useMemo三个hooks函数在React中的简单运用场合和区别。更多资料也可以参照官网的解释:

https://react.docschina.org/docs/hooks-reference.html

https://react.docschina.org/docs/hooks-reference.html

https://react.docschina.org/docs/hooks-reference.html

声明:文中观点不代表本站立场。本文传送门:http://eyangzhen.com/247245.html

联系我们
联系我们
分享本页
返回顶部