1. useState
使用方法
const [n, setN] = React.useState(0)
const [user, setUser] = React.useState({name:'F'})
注意事项
- setState不会帮我们合并属性
- useReducer也不会合并属性
- 地址要变
- setState(obj)如果obj内存地址不变,那么React就认为数据没有变化
- 该函数返回初始state,且只执行一次
- 基本写法:setN( 1=>1+1)}
- 什么时候用这种方式?
- 如果你能接受这种形式,应该优先使用这种形式
setState 可接受函数
const [n, setN] = useState(0)
const onClick = ()=>{
// 你会发现 n 不能加 2
setN(n+1)
setN(n+1)
// 需要以下写法
setN(i=>i+1)
setN(i=>i+1)
}
useState 可接受函数
const [state, setState] = useState(()=>{
return initialState
})
不可局部更新
const [user,setUser] = useState({name:'Evan', age: 18})
const onClick = ()=>{
// 不支持局部刷新
setUser({
name: 'Yan',
})
// 需要先拷贝,并重新生成对象地址
setUser({
...user,
name: 'Yan',
})
}
总结
- React只做它该做的事情,或者只提供工具,把值拷贝这类操作交给开发者
- 函数是一等公民,所以useState和setState都可接受函数为参数,且优先推荐
2. useReducer
使用方法
// 共分4步走
// 一、创建初始值initialState
const initial = {
n: 0
};
// 二、创建所有操作reducer(state, action)
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() {
// 三、传给useReducer,得到读和写API
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const onClick = () => {
// 四、调用写({type:操作类型'})
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>
);
}
- 用来践行Flux/Redux的思想
- 总得来说useReducer是useState的复杂版
- 一个用useReducer的表单例子
如何代替Redux
- 将数据集中在一个store对象
- 将所有操作集中在reducer
- 创建一个Context
- 创建对数据的读写API
- 将第四步的内容放到第三步的Context
- 用Context.Provider将Context提供给所有组件
- 各个组件用useContext获取读写API
3. useContext
上下文
- 全局变量是全局的上下文
- 上下文是局部的全局变量
使用方法
- 使用C = createContext(initial)创建上下文
- 使用<C.provider>圈定作用域
- 在作用域内使用useContext(C)来使用上下文
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>
);
}
注意事项
不是响应式的
- 你在一个模块将C里面的值改变
- 另一个模块不会感知到这个变化
4. useEffect
副作用
- 对环境的改变即为副作用,如修改document.title
- 但我们不一定非要把副作用放在useEffect里
- 实际上叫做afterRender更好,每次render后运行
用途
- 作为componentDidMount使用,[]作第二个参数
- 作为componentDidUpdate使用,可指定依赖,如[a, b]
- 作为componentWillUnmount使用,通过return
- 以上三种用途可同时存在
特点
- 如果同时存在多个useEffect,会按照出现次序执行
5. useLayoutEffect
布局副作用
- useLayoutEffect在浏览器渲染前执行
通过时间点来侧面证明 (此处要画图说明)
function App() {
const [n, setN] = useState(0)
const time = useRef(null)
const onClick = ()=>{
setN(i=>i+1)
time.current = performance.now()
}
useLayoutEffect(()=>{ // 改成 useEffect 试试
if(time.current){
console.log(performance.now() - time.current)
}
})
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}
useEffect在浏览器渲染完成后执行
const BlinkyRender = () => {
const [value, setValue] = useState(0);
useEffect(() => {
document.querySelector('#x').innerText = `value: 1000`
}, [value]);
return (
<div id="x" onClick={() => setValue(0)}>value: {value}</div>
);
};
特点
- useLayoutEffect里的任务最好影响了Layout
useLayoutEffect总是比useEffect先执行
function App() {
const [n, setN] = useState(0)
const onClick = ()=>{
setN(i=>i+1)
}
useEffect(()=>{
console.log("useEffect")
})
useLayoutEffect(()=>{ // 改成 useEffect 试试
console.log("useLayoutEffect")
})
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}
经验
- 为了用户体验,优先使用useEffect (优先渲染)
6. useMemo
用法
- 要理解React. useMemo需要先理解React.memo
- React默认有多余的render
- 讲代码中的Child用React.memo(Child)代替
- 如果props不变,就没有必要再次执行一个函数组件
最终效果:
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);
问题
- 添加了监听函数之后,一秒破功
- 因为App运行时会在次执行第12行,生成新的函数
- 新旧函数虽然功能一样,但是地址不一样!
怎么办?用useMemo:
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClick2 = () => {
setM(m + 1);
};
const onClickChild = useMemo(() => {
const fn = div => {
console.log("on click child, m: " + m);
console.log(div);
};
return fn;
}, [m]); // 这里呃 [m] 改成 [n] 就会打印出旧的 m
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update m {m}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
这玩意有一个bug
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClickChild = () => {
console.log(m);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
{/* Child2 居然又执行了 */}
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={props.onClick}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
特点
- 第一个参数是()=> value,见定义
- 第二个参数是依赖[m,n]
- 只有当依赖变化时,才会计算出新的value
- 如果依赖不变,那么就重用之前的value
- 这不就是Vue 2的computed吗?
注意
- 如果你的value是个函数,那么你就要写成useMemo(()=> (x) => console.1og(x))
- 这是一个返回函数的函数
- 是不是很难用?于是就有了useCallback
7. useCallback
用法
- useCallback(x => log(x), [m]) 等价于
- useMemo(() => x => 1og(x), [m])
8. useRef
目的
- 如果你需要一个值,
- 在组件不断render时保持不变
- 初始化: const count = useRef(0)
- 读取: count.current
- 为什么需要current?
- 为了保证两次useRef是同一个值(只有引用能做到)
- 此处需要画图解释
延伸
- 看看Vue3的ref
- 初始化: const count = ref(0)
- 读取: count.value
- 不同点:当count.value变化时,Vue 3会自动render
能做到变化时自动render吗?
- 不能!
- 为什么不能?因为这不符合React的理念
- React的理念是UI = f(data)
- 你如果想要这个功能,完全可以自己加
- 监听ref,当ref.current变化时,调用setX即可
不想自己加?
- 那你就用Vue3吧,Vue3帮你加好了
9. forwardRef
讲了useRef就不得不讲一下它了
10. useRef
- 可以用来引用DOM对象
- 也可以用来引用普通对象
11. forwardRef
- 由于props不包含ref,所以需要forwardRef
- 为什么props不包含ref呢?因为大部分时候不需要
12. uselmperativeHandle
名字起得稀烂
分析
- 用于自定义ref的属性
13. 自定义Hook
封装数据操作
- 简单例子
- 贴心例子
分析
- 你还可以在自定义Hook里使用Context
- useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可
14. Stale Closure
- 过时闭包
- 参考文章链接
本篇已被阅读 次