React函数式组件值之useState()
react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他 react 特性,而不仅限于 class 组件。react hooks 的出现,标示着 react 中不会在存在无状态组件了,只有类组件和函数组件。
状态是隐藏在组件中的信息,组件可以在父组件不知道的情况下修改其状态。相比类组件,函数组件足够简单,要使函数组件具有状态管理,可以useState() Hook。
一、基础用法
定义一个state,以及更新 state 的函数。在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
1 const [state, setState] = useState(initialState);
2 setState(newState);
例子:
1 function App () {
2 const [ count, setCount ] = useState(0)
3 return (
4 <div>
5 点击次数: { count }
6 <button onClick={() => { setCount(count + 1)}}>点我</button>
7 </div>
8 )
9 }
当我们在使用 useState 时,修改值时传入同样的值,组件不会重新渲染,这点和类组件setState保持一致。
二、初始值与初始函数
useState 支持我们在调用的时候直接传入一个值,来指定 state 的默认值,比如这样 useState(0), useState({ a: 1 }), useState([ 1, 2 ]),还支持我们传入一个函数,来通过逻辑计算出默认值,比如这样
1 function App (props) {
2 const [ count, setCount ] = useState(() => {
3 return props.count || 0
4 })
5 return (
6 <div>
7 点击次数: { count }
8 <button onClick={() => { setCount(count + 1)}}>点我</button>
9 </div>
10 )
11 }
useState 中的函数只会在初始化的时候执行一次。
三、函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:
1 function Counter() {
2 const [count, setCount] = useState(0);
3 function handleClick() {
4 setCount(count + 1)
5 }
6 function handleClickFn() {
7 setCount((prevCount) => {
8 return prevCount + 1
9 })
10 }
11 return (
12 <>
13 Count: {count}
14 <button onClick={handleClick}>+</button>
15 <button onClick={handleClickFn}>+</button>
16 </>
17 );
18 }
handleClick和handleClickFn一个是通过一个新的 state 值更新,一个是通过函数式更新返回新的 state。现在这两种写法没有任何区别,但是如果是异步更新的话,区别就显现出来了。
1 function Counter() {
2 const [count, setCount] = useState(0);
3 function handleClick() {
4 setTimeout(() => {
5 setCount(count + 1)
6 }, 3000);
7 }
8 function handleClickFn() {
9 setTimeout(() => {
10 setCount((prevCount) => {
11 return prevCount + 1
12 })
13 }, 3000);
14 }
15 return (
16 <>
17 Count: {count}
18 <button onClick={handleClick}>+</button>
19 <button onClick={handleClickFn}>+</button>
20 </>
21 );
22 }
当设置为异步更新,点击按钮延迟到3s之后去调用setCount函数,当快速点击按钮时,也就是说在3s多次去触发更新,但是只有一次生效,因为 count 的值是没有变化的。而当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。
其实这个特点和类组件中 setState 类似,可以接收一个新的 state 值更新,也可以函数式更新。如果新的 state 需要通过使用先前的 state 计算得出,那么就要使用函数式更新。因为setState更新可能是异步,当你在事件绑定中操作 state 的时候,setState更新就是异步的。
一般操作state,因为涉及到 state 的状态合并,react 认为当你在事件绑定中操作 state 是非常频繁的,所以为了节约性能 react 会把多次 setState 进行合并为一次,最后在一次性的更新 state,而定时器里面操作 state 是不会把多次合并为一次更新的。
四、使用优化
在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。
1 function Child({ onButtonClick, data }) {
2 console.log('Child Render')
3 return (
4 <button onClick={onButtonClick}>{data.number}</button>
5 )
6 }
7
8 function App() {
9 const [number, setNumber] = useState(0)
10 const [name, setName] = useState('hello') // 表单的值
11 const addClick = () => setNumber(number + 1)
12 const data = { number }
13 return (
14 <div>
15 <input type="text" value={name} onChange={e => setName(e.target.value)} />
16 <Child onButtonClick={addClick} data={data} />
17 </div>
18 )
19 }
上述代码中,子组件引用了number相关数据,但是当name相关数据发生变化,也会重绘整个组件,子组件虽然没有任何变化,也会重绘。为了避免不必要的子组件的重渲染,需要使用useMemo和useCallback的Hook。
1 function Child({ onButtonClick, data }) {
2 console.log('Child Render')
3 return (
4 <button onClick={onButtonClick}>{data.number}</button>
5 )
6 }
7
8 Child = memo(Child)
9
10 function App() {
11 const [number, setNumber] = useState(0)
12 const [name, setName] = useState('hello') // 表单的值
13 const addClick = useCallback(() => setNumber(number + 1), [number])
14 const data = useMemo(() => ({ number }), [number])
15 return (
16 <div>
17 <input type="text" value={name} onChange={e => setName(e.target.value)} />
18 <Child onButtonClick={addClick} data={data} />
19 </div>
20 )
21 }
22
23 export default App;
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
useCallback返回一个 memoized 回调函数。useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useCallback 和 useMemo 参数相同,第一个参数是函数,第二个参数是依赖项的数组。主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。