useState
基本使用:
| const [state, setState] = useState(initialState);
const [n, setN] = React.useState(0); const [user, setUser] = React.useState({ name: '张三' });
|
注意事项:
1. 不可局部更新
如果 state 是一个对象,是不可以合并属性的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React, { useState } from 'react';
function App() { const [user, setUser] = useState({ name: 'Wangpf', age: 18 }); const changeState = () => { setUser({ name: 'Mark', }); }; return ( <div> <h2>名字:{user.name}</h2> <h2>年龄:{user.age}</h2> <button onClick={changeState}>改变</button> </div> ); }
export default App;
|
看代码所示,当我点击按钮时,它会把名字改为 Mark ,但是 age 会显示不见, 这就是因为它不会局部更新,不会帮我们合并属性,因此需要我们自己加上。
| const changeState = () => { setUser({ ...user, name: 'Mark', }); };
|
2. 地址会变
setState(obj),如果obj地址不变,那么 React 就认为数据没有变化。
意思就是,我们不能在原来的数据上进行操作,而是重新写一个新的对象来覆盖之前的。
useState 和 setState 都可以接收函数
| const [user, setUser] = useState(() => { return { name: 'Wangpf', age: 18 }; });
const changeState = () => { setUser((user) => { return { ...user, name: 'Mark', }; }); };
|
useEffect
- Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
- React 中的副作用操作:
发 ajax 请求数据获取
设置订阅 / 启动定时器
手动更改真实 DOM
- 语法和说明:
- ```jsx
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => {
// 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
};
}, [stateValue]); // 如果指定的是[], 回调函数只会在第一次render()后执行
- 可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| useEffect(() => { console.log('第一次渲染之后及之后每次都会执行'); }); useEffect(() => { console.log('只执行第一次渲染之后'); }, []); useEffect(() => { if (count !== 0) { console.log('count变化了'); } }, [count]); useEffect(() => { return () => { componentWillUnmount(); }; }, []);
|
useContext
“上下文”
- 全局变量 是全局的“上下文”
- “上下文”是局部的全局变量
使用步骤:
const XxxContext = React.createContext()
渲染子组时,外面包裹 xxxContext.Provider, 通过 value 属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
后代组件读取数据
- const {数据} = useContext(xxxContext)
适合用于祖孙组件的通信
使用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import React, { createContext, useContext, useState } from 'react';
const Context = createContext(null); function App() { const [count, setCount] = useState(0); return ( <Context.Provider value={{ count, setCount }}> <div> <A /> </div> </Context.Provider> ); } function A() { return ( <div> <h2>我是A组件</h2> <B /> </div> ); } function B() { const { count, setCount } = useContext(Context); const increment = () => { setCount((n) => n + 1); }; return ( <div> <h3>我是B组件</h3> <div>count:{count}</div> <button onClick={increment}>+1</button> </div> ); }
export default App;
|
useReducer
此 hooks API 践行了 Redux 的思想
逻辑步骤:
创建初始值 initialState
创建所有操作 reducer = (state, action)=>{}
传给 useReducer
,得到读写的 API
调用并写 {{type:'操作类型'}}
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const initialState = { count: 0 }; const reducer = (state, action) => { const { type, data } = action; if (type === 'increment') { return { count: state.count + data }; } else if (type === 'decrement') { return { count: state.count - data }; } else { throw new Error('unknown type'); } };
function A() { const [state, dispatch] = useReducer(reducer, initialState); const { count } = state;
const increment = () => { dispatch({ type: 'increment', data: 1 }); }; const decrement = () => { dispatch({ type: 'decrement', data: 2 }); };
return ( <div> <h2>我是A组件</h2> <h4>count:{count}</h4> <button onClick={increment}>加</button> <button onClick={decrement}>减</button> </div> ); }
|
useMemo
通过一个实例,来理解下 useMemo 的用法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import React, { useState } from 'react'; function App() { const [n, setN] = useState(0); const [m, setM] = useState(0); const updateN = () => { setN((n) => n + 1); }; const updateM = () => { setM((m) => m + 1); }; return ( <div> <button onClick={updateN}>update n {n}</button> <button onClick={updateM}>update m {m}</button> <A data={m} /> </div> ); } function A(props) { console.log('A组件执行了'); return <div>A组件:{props.data}</div>; } export default App;
|
注意打印 console.log 的位置。
当我们点击 APP 组件的更新 N 的按钮时候,A 组件会发生改变,但是 M 是没有改变的啊。
结论:不管我们是否改变了 A 组件的数据,我们会发现 A 组件会被重新渲染的。这就意味着性能的损耗。
因此,为了优化这个,React.memo 就派上用场了。
只需把 A 组件改为:
| const A = React.memo((props) => { console.log('A组件执行了'); return <div>A组件:{props.data}</div>; });
|
这样,只有 APP 组件的 M 数据不发生改变,那么 A 组件就不会重新渲染。
但是!!这样有个 BUG,
当我给 A 组件绑定一个事件时,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import React, { useState, memo } from 'react';
function App() { const [n, setN] = useState(0); const [m, setM] = useState(0); const updateN = () => { setN((n) => n + 1); }; const updateM = () => { setM((m) => m + 1); }; const changeA = () => {}; return ( <div> <button onClick={updateN}>update n {n}</button> <button onClick={updateM}>update m {m}</button> <A data={m} onClick={changeA} /> </div> ); }
const A = memo((props) => { console.log('A组件执行了'); return <div onClick={props.changeA}>A组件:{props.data}</div>; }); export default App;
|
这样,即时我不该关于 A 组件的任何数据,A 组件也会随着发生渲染,
问题的原因是因为:当我们重新渲染 APP 组件时,它会自动调用该 changeA 事件,那么它就会改变 A 组件的渲染。
解决该问题的方法就是: 使用 useMemo
| const changeA = useMemo(() => {}, []);
|
总结
特点
第一个参数是 () => value
第二参数是 依赖 [xx]
只有当依赖变化时,会计算出新的 value
如果依赖不变,那么就重用之前的 value
是不是 Vue 中的 computed?
注意项:
useCallback
下面俩个是等价的。
| const changeA = useMemo(() => { return (m) => console.log(m); }, []);
const changeA = useCallback((m) => console.log(m), []);
|
可以理解为useCallback
缓存的是函数,而useMemo
缓存的是一个值,当然这个值可以是函数,在使用useCallback
时还需要注意所谓闭包陷阱的问题,见如下代码:
| const changeNum = useCallback(()=>{ setNum(num+1); },[])
const changeNum = useCallback(()=>{ setNum(num+1); },[num])
const numRef = useRef() numRef.current = num const changeNum = useCallback(()=>{ setNum(numRef.current+1); },[])
|
useRef
useRef 返回的是一个可变的 ref 对象,其 xx.current 属性也就是 useRef(inital)中的 inital 初始化值。
所以注意 const xxxRef = useRef(0) 中, 此处的 0 实际是个对象为:{current:0}
为什么是对象的原因请看用处二
用处一: 获取 元素,可以做一些操作元素之类
通过如下简单实例可以明白
| import React, { useRef } from 'react';
function App() { const inputRef = useRef(null); const onclick = () => { console.log(inputRef.current); }; return ( <div> <input type='text' ref={inputRef} /> <button onClick={onclick}>获取input</button> </div> ); }
export default App;
|
用处二:保持可变变量
目的: 如果需要一个值,在组件不断 render 时保持不变
返回的 ref 对象在组件的整个生命周期内保持不变
举个常见的场景: 对定时器的清除操作。
我们需要确保 setInterval 的执行结果 timer 的引用,才能准确的清除对应的定时器。
| import React, { useEffect, useRef } from 'react';
function App() { const timerRef = useRef(); useEffect(() => { timerRef.current = setInterval(() => { console.log('timerRef'); }, 1000); return () => { timerRef.current && clearInterval(timerRef.current); }; }, []);
return <div>App....</div>; } export default App;
|
forwardRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import React, { useRef, forwardRef, useEffect } from 'react'; function App() { const btnRef = useRef(null); useEffect(() => { console.log(btnRef.current); }, []);
return ( <div> <Button ref={btnRef} /> </div> ); } const Button = forwardRef((props, ref) => { return ( <div> <button ref={ref} {...props}> 按钮 </button> </div> ); }); export default App;
|
React 会将 <Button ref={ref} />
元素的 ref 作为第二个参数传递给 React.forwardRef 函数中的渲染函数。
该渲染函数会将 ref 传递给 <button ref={ref} />
元素。
因此,当 React 附加了 ref 属性之后,inputRef.current 将直接指向 button
DOM 元素实例。
useImperativeHandle
如果想要给给该组件的属性改个名字,或者返回其他额外的属性或者方法,我们可以使用 useImperativeHandle。
这个 API 具体我没去深入了解。
自定义 Hook
做一个 hooks 来实现渲染列表以及删除列表
组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import React from 'react'; import useList from './hooks/useList'; function App() { const { list, deleteIndex } = useList(); return ( <div> <h2>小说列表</h2> {list ? ( <ul> {list.map((item, index) => ( <li key={item.id}> {item.name} <button onClick={() => { deleteIndex(index); }} > 删除 </button> </li> ))} </ul> ) : ( <span>加载中....</span> )} </div> ); } export default App;
|
自定义 hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { useState, useEffect } from 'react' import {nanoid } from 'nanoid'
const useList = () => { const [list, setList] = useState(null) useEffect(() => { ajax().then(list => { setList(list) }) }, []) return { list: list, setList: setList, deleteIndex: index => { setList(list.slice(0,index).concat(list.slice(index+1))) } } } export default useList function ajax() { return new Promise((resolve, reject) => { setTimeout(() => { resolve([ {id: nanoid(),name:"小王子"}, {id: nanoid(),name:"明朝那些事儿"}, {id: nanoid(),name:"三体"}, ]) },2000) }) } ```
|