【前端丛林】React这样服用,效果更佳(6)
前言
哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家再次来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。今天是国庆假期第六天,也是我们冒险的第六天,今天我们会继续深入学习React高级用法,而且是React的重点React Hooks。Hook 是 React 16.8 的新增特性,学会React Hooks将大大提高我们的开发效率,所以一定要掌握好React Hooks。国庆假期进入尾声了,不知道大家在假期里学得怎样了呢?在这七天假期里,让我们稳住继续学习,学会React实现弯道超车。你们准备好了吗?那么开始我们的冒险之旅吧!
1.React Hooks
- Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的React 特性
- 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为class。现在你可以在现有的函数组件中使用 Hook
2. 解决的问题
- 在组件之间复用状态逻辑很难,可能要用到render props和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
- 复杂组件变得难以理解,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- 难以理解的 class,包括难以捉摸的 this
3. 注意事项
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
4. useState
- useState 就是一个 Hook
- 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state
- useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state进行合并
- useState 唯一的参数就是初始 state
- 返回一个 state,以及更新 state 的函数
- 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
- setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列
const [state, setState] = useState(initialState);
4.1 计数器
下面我们就用useState来简单实现一个计数器
import React, { useState } from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({ number: this.state.number + 1 })}>
+
</button>
</div>
);
}
}
function Counter2() {
const [number, setNumber] = useState(0);
return (
<>
<p>{number}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
</>
)
}
export default Counter2;
4.2 每次渲染都是独立的闭包
- 每一次渲染都有它自己的 Props and State
- 每一次渲染都有它自己的事件处理函数
- alert会“捕获”我点击按钮时候的状态
- 我们的组件函数每次渲染都会被调用,但是每一次调用中number值都是常量,并且它被赋予了当前渲染中的状态值
- 在单次渲染的范围内,props和state始终保持不变
function Counter() {
const [number, setNumber] = useState(0);
const savedCallback = useRef();
function alertNumber() {
setTimeout(() => {
alert(savedCallback.current);
}, 3000);
}
return (
<>
<p>{number}</p>
<button onClick={() => {
setNumber(number + 1);
savedCallback.current = number + 1;
}}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
function Counter2() {
const [number, setNumber] = useState(0);
function alertNumber() {
setTimeout(() => {
alert(number);
}, 3000);
}
return (
<>
<p>{number}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
4.3 函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值
function Counter2() {
const [number, setNumber] = useState(0);
let numberRef = useRef(number);
numberRef.current = number;
function alertNumber() {
setTimeout(() => {
alert(numberRef.current);
}, 3000);
}
function lazy() {
setTimeout(() => {
setNumber(number + 1);
}, 3000);
}
function lazyFunc() {
setTimeout(() => {
setNumber(number => number + 1);
}, 3000);
}
return (
<>
<p>{number}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
<button onClick={lazy}>lazy+</button>
<button onClick={lazyFunc}>lazyFunc+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
4.4 惰性初始 state
- initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略
- 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用
- 与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的setState 结合展开运算符来达到合并更新对象的效果
function Counter3() {
const [{ name, number }, setValue] = useState(() => {
return { name: '计数器', number: 0 };
});
return (
<>
<p>{name}:{number}</p>
<button onClick={() => setValue({ number: number + 1 })}>+</button>
</>
)
}
4.5 性能优化
4.5.1 Object.is
调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state)
function Counter4() {
const [counter, setCounter] = useState({ name: '计数器', number: 0 });
console.log('render Counter')
return (
<>
<p>{counter.name}:{counter.number}</p>
<button onClick={() => setCounter({ ...counter, number: counter.number + 1 })}>+</button>
<button onClick={() => setCounter(counter)}>-</button>
</>
)
}
4.5.2 减少渲染次数
- 把内联回调函数及依赖项数组作为参数传入 useCallback ,它将返回该回调函数的 memoized版本,该回调函数仅在某个依赖项改变时才会更新
- 把创建函数和依赖项数组作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
function Child({ onButtonClick, data }) {
console.log('Child render');
return (
<button onClick={onButtonClick} >{data.number}</button>
)
}
Child = memo(Child);
function App() {
const [number, setNumber] = useState(0);
const [name, setName] = useState('小明');
const addClick = useCallback(() => setNumber(number + 1), [number]);
const data = useMemo(() => ({ number }), [number]);
return (
<div>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
<Child onButtonClick={addClick} data={data} />
</div>
)
}
4.6 注意事项
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function App() {
const [number, setNumber] = useState(0);
const [visible, setVisible] = useState(false);
if (number % 2 == 0) {
useEffect(() => {
setVisible(true);
}, [number]);
} else {
useEffect(() => {
setVisible(false);
}, [number]);
}
return (
<div>
<p>{number}</p>
<p>{visible && <div>visible</div>}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
结尾
好啦,这期的前端丛林大冒险先到这里啦!这期我们介绍了React Hooks以及useState这一钩子函数,让大家对React Hooks有了初步的了解,大家一定要好好啃下来嚼烂嚼透。希望大家可以好好品尝并消化,迅速升级,接下来我们才更好地过五关斩六将!好啦,我们下期再见。拜拜!
转载自:https://juejin.cn/post/7151411906035580958