1、React Hooks
- useState
- useEffect
- useContext
- useReducer
- useMemo
- useRef
- 自定义 Hook
2、useState
- 使用
const [n,setN] = useState(0)
const [user,setUser] = useState({name:'f'})
- 注意事项1:不可局部更新
- setState 不会帮我们合并属性
- 需要加上 ...state
- 示例:
function App() {
const [user,setUser] = useState({name:'Frank', age: 18})
const onClick = ()=>{
setUser({
name: 'Jack'
})
}
return (
<div className="App">
<h1>{user.name}</h1>
<h2>{user.age}</h2>
<button onClick={onClick}>Click</button>
</div>
);
}
//解决方法
const onClick = ()=>{
setUser({
...user
name: 'Jack'
})
}
- 注意事项2:地址要变
- setState(obj) 如果 obj 地址不变,那么 React 不会去查看对象中的内容是否变化,就不会做出改变
- 示例:
function App() {
const [user,setUser] = useState({name:'Frank', age: 18})
const onClick = ()=>{
user.name = 'jack'
//React 不会查看对象的内容变化
setUser(user)
}
return (
<div className="App">
<h1>{user.name}</h1>
<h2>{user.age}</h2>
<button onClick={onClick}>Click</button>
</div>
);
}
//解决方法:生成一个新的对象
const onClick = ()=>{
setUser({
...user
name: 'Jack'
})
}
- useState 接受函数
- 写成函数的好处:减少多余计算过程
- 示例:
const [state, setState] = useState(()=> ({name:'wbh',age:18}) )
//该函数返回初始 state,且只执行一次
- setState 接受函数(优先使用)
setN(i => i+1)
3、useReducer
- 用来践行 Flux/Redux 的思想
- 分四步走
- 1、创建初始值 initialState
- 2、创建所有操作 reducer(state,action)
- 3、传给 useReducer,得到读和写 API
- 4、调用写 ({type:'操作类型'})
- 总的来说就是 useState 的复杂版本
- 示例:
import React, {useReducer } from "react";
import ReactDOM from "react-dom";
const initialState = {
n:0
}
const reducer = (state, action) => {
if(action.type === 'add'){
return {n: state.n + action.number}
}else if(action.type === 'multi'){
return {n: state.n + action.number}
}else{
throw new Error('unknown type');
}
}
function App() {
const [state,dispatch] = useReducer(reducer,initialState)
const onClick = () => {
dispatch({type:'add',number:1})
};
const onClick2 = () => {
dispatch({type:'multi',number:2})
};
return (
<div className="App">
<h1>n:{state.n}</h1>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>*2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
4、如何代替 Redux
- 步骤
-
- 将数据集中在一个 store 对象
-
- 将所有操作集中在 reducer
-
- 创建一个 Context
-
- 创建对数据的读写 API
-
- 将第四步的内容放到第三步的 Context
-
- 用Context.Provider 将 Context 提供给所有组件
-
- 各个组件用 useContext 获取读写 API
-
- 也就是 useReducer 和 useContext 的结合
5、useContext
- 上下文
- 全局变量是全局的上下文
- 上下文是局部的全局变量
- 使用方法
- 1、使用 C = createContext(null) 创建上下文
- 2、使用 <C.Provider> 圈定作用域
- 3、在作用域内使用 useContext(C) 来使用上下文
- 示例:
const C = createContext(null)
function App() {
const [n,setN] = useState(0)
return (
<C.Provider value={{n:n,setN:setN}}>
<div>
我是爷爷:
<Baba />
</div>
</C.Provider>
)
}
function Baba(){
return (
<div>
我是爸爸,
<Child />
</div>
)
}
function Child(){
const {n,setN} = useContext(C)
const onClick = () =>{
setN(i => i+1)
}
return (
<div>
我是儿子,n:{n}
<button onClick={onClick}>+1</button>
</div>
)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
- 注意:useContext 不是响应式的,是一个 Child 组件变化后,逐级通知,然后重新渲染 App 的过程
6、useEffect
-
副作用
- 对环境的改变即为副作用,如修改 document.title
- 实际上叫做 afterRender 更好,每次 render 后运行
-
用途
- 作为 componentDidMount 使用,[] 作为第二个参数
- 作为 componentDidUpdate ,可指定依赖
- 作为 componentWillUnmount 使用,通过return
-
以上三种用途可同时存在
-
特点
- 如果同时存在多个 useEffect,会按照出现次序执行
7、useLayoutEffect
- 布局副作用
- useEffect 在浏览器渲染完成后执行
- useLayoutEffect 在浏览器渲染前执行
- 特点:
- useLayoutEffect 总是比 useEffect 先执行
- useLayoutEffect 里的任务最好影响了输出外观
- 建议:
- 为了用户体验,优先使用 useEffect
8、useMemo
- React.memo
- React 默认会出现多余的 render,当一个App重新渲染时,其中的子组件也会重新渲染
- 那么,如果 props 不变,就没有必要再次执行一个函数组件,使用 React.memo
- 示例:
import React from "react";
import ReactDOM from "react-dom";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClick2 = () => {
setM(m => m + 1);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update m {m}</button>
</div>
<Child data={m}/>
</div>
);
}
const Child = React.memo( props => {
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
- 但是 React.memo 存在 bug
- 添加监听函数后,直接失效
- 因为 App 运行时会再次生成新的 onClickChild
- 新旧函数虽然功能一样,但是地址不一样
- 示例:
function App() {
...
const onClickChild = () => {}
return (
...
<Child data={m} onClick={onClick3}/>
);
}
- 解决方法:使用 useMemo 来调用对应的监听事件
const onClickChild = useMemo(()=>{
return ()=>{
...
}
},[m])
//这样就只会在 m 改变时,重新生成 onClickChild 函数
-
useMemo 特点
- 第一个参数是 () => value
- 第二个参数是依赖 [m,n]
- 只有当依赖变化时,才会计算出新的 value
- 依赖不变时,就会重用之前的 value
-
注意
- 如果 value 是一个函数,那么就需要写成
useMemo(() => {return ()=>{...} },[n]) - 这是一个返回函数的函数
- 于是出现了 useCallback
- 如果 value 是一个函数,那么就需要写成
-
useCallback
- 用法:
useCallback(()=>{...} ,[n])
//等价于,省略了第一个 () =>
useMemo(() => {return ()=>{...}},[n])
9、useRef
- 目的
- 如果需要一个值,在组件不断 render 的过程中保持不变
- 初始化:const count = useRef(0)
- 读取:count.current
- 为什么需要 current
- 为了保证组件渲染后的 count.current 是同一个值(只有引用能做到)
- 注意:在 React 中,count 变化后并不会渲染组件,只能通过 setState 的方法来渲染
10、forwardRef
- 示例:
import React, { useRef } from "react";
import ReactDOM from "react-dom";
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
- 由于 props 不包含 ref ,所以需要 forwardRef
- 但是,大部分时候不需要 ref