React Hooks
- 状态 usestate
- 副作用 useEffect
- 上下文 useContext
- Redux useReducer
- 记忆 useMemo
- 引用 useRef
- 自定义Hook
useState
使用状态
const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:'xiaoming'})
不可局部更新
如果state是一个对象,是不能部分 setState 的。
当点击按钮
age 就会消失。
因为setState 不会帮我们合并属性
可采用这种方法:
地址要变
setState(obj) ,如果obj地址不变,那么 React 就认为数据没有变化。
useState 可接受函数
当初始值比较复杂时,可采用。
const [state,setState] = useState(()=>{
return initialState
})
该函数返回初始 state ,且只执行一次。
setState 接受函数
setN(i=>i+1)
什么时候采用这种方式呢?
import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
const [n, setN] = useState(0)
const onClick = ()=>{
setN(n+1) // setN(n+1) 并不会使得 n 改变
setN(n+1) // 你会发现 n 不能加 2
// setN(i=>i+1)
// setN(i=>i+1) // 采用这种方式就可实现加2
}
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>+2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
其实应该使用这种形式。
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
import React, { useState, useReducer } from "react";
import ReactDOM from "react-dom";
const initial = {
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 * 2 };
} else {
throw new Error("unknown type");
}
};
function App() {
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const onClick = () => {
dispatch({ type: "add", number: 1 });
};
const onClick2 = () => {
dispatch({ type: "add", number: 2 });
};
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>+2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
总的来说,useReducer 是 useState 的复杂版
useContext
- 使用 C = createContext(initial) 创建上下问
- 使用 <C.provider> 圈定作用域
- 在作用域内 使用useContext(C) 来使用上下问
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const C = createContext(null);
function App() {
console.log("App 执行了");
const [n, setN] = useState(0);
return (
<C.Provider value={{ n, setN }}>
<div className="App">
<Baba />
</div>
</C.Provider>
);
}
function Baba() {
const { n, setN } = useContext(C);
return (
<div>
我是爸爸 n: {n} <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);
useEffect
副作用
- 对环境的改变即为副作用,如修改 document.title
- 不一定非要把副作用放在 useEffect 里
- 实际上叫做 afterRender 可能更好,每次 render 后执行
用途
- 作为componentDidMount 使用,[]作第二个参数
- 作为 componentDidUpdate 使用, 可指定依赖
- 作为componentWillUnmount 使用,通过 return
- 以上三种用途可以同时存在
特点
如果同时存在多个 useEffect ,会按照出现次序执行。
useLayoutEffect
useEffect 在浏览器渲染完成后执行
useLayoutEffect 在浏览器渲染前执行
特点
- useLayoutEffect 总是比 useEffect 先执行
- useLayoutEff 里的任务最好影响了 Layout
为了用户体验,优先使用 useEffect
useMemo
React.memo
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m}/>
{/* <Child2 data={m}/> */}
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
React默认有多余的render,点击按钮,Child() 执行了,但Child 依赖数据并没有改变,此时,可使用React.memo(Child)代替 Child。
如果props不变,就没必要再执行一个函数组件。
但是,React.memo有个 bug
const onClickChild = ()=>{}
把一个监听函数传给这个组件时,即使监听函数什么也不做,每次当外部组件数据改变重新渲染时,这个组件也会执行。
这是因为每次重新执行 App() ,都会生成一个新的监听函数,和之前的监听函数地址不同,所以会导致这个组件也执行。
使用useMemo可以解决这个问题
const onClickChild = useMemo(()=>{ return console.log(m) },[m])
useMemo特点
- 第一个参数是 ()=> value
- 第二个参数是依赖[m,n]
- 只有当依赖变化时,才会计算出新的 value,如果依赖不变,那么就重用之前的value
注意
如果你的 value 是个函数,那么就要写成 useMemo(()=> ()=> console.log(x))
这是一个返回函数的函数,很难用,于是有了 useCallback
useCallback
用法
useCallback(x=>log(x),[m]) 等价于 useMemo(()=>x=>log(x),[m])
useRef
目的
- 如果需要一个值,在组件不断 render 时保持不变
- 初始化: const count=useRef(0)
- 读取:count.current
- 为什么需要current ,为了保证两次useRef是同一个值(只有引用能做到)
forwardRef
props无法传递ref属性。
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
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);
useImperativeHandle
用于自定义 ref 的属性
const Button2 = React.forwardRef((props, ref) => {
const realButton = createRef(null);
const setRef = useImperativeHandle;
setRef(ref, () => {
return {
x: () => {
realButton.current.remove();
},
realButton: realButton
};
});
return <button ref={realButton} {...props} />;
});
自定义 Hook
封装数据操作
hooks/useList.js
import { useState, useEffect } from "react";
const useList = () => {
const [list, setList] = useState(null);
useEffect(() => {
ajax("/list").then(list => {
setList(list);
});
}, []); // [] 确保只在第一次运行
return {
list: list,
setList: setList
};
};
export default useList;
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: 1, name: "Frank" },
{ id: 2, name: "Jack" },
{ id: 3, name: "Alice" },
{ id: 4, name: "Bob" }
]);
}, 2000);
});
}
index.js
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";
function App() {
const { list, setList } = useList();
return (
<div className="App">
<h1>List</h1>
{list ? (
<ol>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);