学习内容:
- 状态 useState
- 副作用 useEffect useLayoutEffct
- 上下文 useContext
- Redux useReducer
- 记忆 useMemo 回调useCallback
- 引用 useRef useimperativeHandle
- 自定义Hook useDebugValue
一、useState
useState 改变状态
1. useState不可局部更新
const App =()=>{
const [user,setN] = React.useState({name:'yuyuan',age:18})
const onClick = ()=>{
setN({...user,age:user.age+1}) //不继承user的话,name会被undefined
}
}
2. setState地址要变
const onClick =()=>{
// setN({...user,age:user.age+1})
user.age +=1 //这就是错误写法了
}
3. useState接受函数作为参数传入
const [user,setN] = React.useState(()=>({name:'yuyuan',age:9+9}))
4. setState接受函数作为参数传入
const onClick = ()=>{
setN((user)=>({...user,age:user.age+1}))
setN((user)=>({...user,age:user.age+1}))
}
二、useReducer
import React 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==='divide'){
return {n:state.n-action.number}
}else{
throw new Error('unknow type')
}
}
const App =()=>{
const [state,dispatch] = React.useReducer(reducer,initial)
const {n} = state
const onClick = ()=>{
dispatch({type:'add',number:1})
}
const onClick2 =()=>{
dispatch({type:'add',number:2})
}
return (
<div>
<p>{n}</p>
<p><button onClick={onClick}>+1</button></p>
<p><button onClick={onClick2}>+2</button></p>
</div>
)
}
ReactDOM.render(<App/>,document.getElementById('root'))
(真的难写,useReducer:复杂版的useState)
三、如何代替Redux
题外知识:关于析构赋值
字符串、数组中
//按顺序用[]赋值就好了
var x = [1, 2, 3, 4, 5];
var [y, z] = x;
console.log(y); // 1
console.log(z); // 2
对象中
//标明key,来对应赋值
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'
用useReducer和Context代替Redux
- 先把依赖的都搞出去,例如ajax和Context,在 index.js中引入;
- 然后把组件抛出去,组件中和index.js中都要注意引入依赖;
- 然后把reducer中的方法写出去,写出对象中的函数,然后继承过来,然后再匹配使用
四、useContext
useContext的三句表达式
1. 创建上下文
const C = React.createContext(null)
2.确定作用域和传入数据
<C.Provider value={{n:n,setN:setN}}>……</C.Provider>
3.作用域中使用上下文
const {n,setN} = React.useContext(C)
const {n} = React.useContext(C)
useContext的注意事项
不是响应式的 : 你在一个模块中改变C中的数据,另一个模块不会感受到这个变化,是重新渲染后逐级通知的,才知道。
五、useEffect
effect:副作用——改变环境
用途:弥补函数组件中缺失生命周期的设置
- 作为componentDidMount()使用,[]作为第二个参数
- 作为componentDidUpdate()使用,[]中可指定依赖,不写[]即为全部
- 作为componentWillUnmount()使用,通过return
特点:如果同一生命周期类型的useEffect,会按照出现次序执行
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [n, setN] = React.useState(0);
const onClick = () => {
setN((x) => x + 1);
};
useEffect(() => {
console.log("first render");
}, []); //第一次渲染执行
useEffect(() => {
console.log("render都执行");
}, []); //渲染即执行
useEffect(() => {
if (n > 0) {
console.log("变化后执行");
}}, [n]); //依赖n
useEffect(() => {
const id = setTimeout(() => {
console.log("我是延时函数");
}, 2000);
return window.clearTimeout(id);
}, []); //走的时候带走垃圾
useEffect(() => { console.log(1);}, []);
useEffect(() => {console.log(2);}, []);
//肯定先打印1,后打印2的啊
return (
<div>
n:{n} <button onClick={onClick}>+1</button>
</div>
)};
ReactDOM.render(<App />, document.getElementById("root"));
六、useLayoutEffect
写一个useLayoutEffect的和useEffect对比就知道是干什么的了,就是执行顺序的差别。
useEffect是创建完虚拟DOM和真实的DOM,UI渲染完成后执行;useLayoutEffect是在真实DOM创建后立即执行。
所以,useLayoutEffect总是比useEffect先执行。实例
useLayoutEffect如果不改变Layout布局,就最好不要用。
Hook API 索引 – React (reactjs.org)
七、useMemo
1.memo初次使用
作用
react中有多余的render,如果props不变,就没必要再执行一个函数组件,于是使用React.memo对代码进行优化
const Child = (props)=>{
console.log('内部代码')
return (
<div>{props.data}</div>
)
}
//React.memo优化 这样props.data值不变,Child就不会重新执行
const Child = React.memo(props=>{
console.log('内部代码')
return (
<div>{props.data}</div>
)
})
useMemo与监听函数
React.memo中写监听函数,会导致失效,即这个被memo的函数组件没有变化时也会被渲染,看实例:memo失效案例
const App =()=>{
……//原组件写个数据变化和个监听函数就行了
return (
……
<Child2 m={m} onClickM={onClickM}>
)
}
const Child = (props)=>{
console.log('我是Child组件')
return (
<div>
<p>m:{props.m}</p>
<button onClick={props.onClickM}>m+1</button>
</div>
)
}
const Child2 = React.memo(Child)
为什么会触发组件更新呢?函数是个对象,虽然m和setM的值没变就没变,而函数是个对象,地址变了就是变了。而每次App组件重新渲染的时候,都会生成新的函数地址,也就是新的函数,从而导致Child也被莫名奇妙的更新。
而useMemo可以实现函数的重用
useMemo的使用
useMemo不是写在组件上,而是写在你希望缓存的函数上。
React.useMemo(()=>value,[m])
- 第一个参数是一个函数,()=>value,如果value是一个函数,就要写成
const onClickM = React.useMemo(()=>(x)=>setM(x+1),[m])
- 第二个参数是它的依赖[m,n],只有依赖变化,才会重新计算出新的value
- 第一个参数中的()=>value,()=>非常多余,于是有了语法糖,useCallback
useCallback(x=>console.log(x),[n])
//等价于
useMemo(()=>x=>console.log(x),[n])
八、useRef
作用:
如果你希望有一个值在render的时候保持不变,可以使用useRef
- 初始化数值
const count = React.useRef(0)
- 读值和改值
count.current += 1
console.log(count.cuurent)
为什么读值和改值需要用current
因为 React.useRef(0)相当于: React.useRef({current:0})
如果不使用current会直接修改对象的地址,如果使用current就会修改对象的值,不会改变原对象的地址。
React中的数据处理函数
- useState/useReducer 每次渲染App(),n都会发生改变
- useMemo/useCallback [m]依赖对象变化的时候,才会发生改变
- useRef 永远不变
所以,useRef不会去刷新UI,如果需要刷新UI,需要你手动去设置,例如使用us eState中的set
九、forwardRef
forwardRed相当于是useRef功能的扩展,当props传递参数的时候,是无法传递ref的,forwardRef就是来弥补useRef的这个功能缺陷的。
const Button = React.forwardRef((props,ref)=>{
console.log(props)
console.log(ref)
return (
<div>
<button ref = {ref}>{props.children}</button>
</div>
)
})
十、useImperativeHandle
这个名字太垃圾了,叫setRef就好多了。useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。可以自定义ref属性。
useImperativeHandle的语法:
useImperativeHandle(ref, createHandle, [deps])
const App =()=>{
const buttonRef = React.createRef(null)
return (
<div className = 'App'>
<Button ref={buttonRef}>按钮</Button>
<button className='close' onClick={()=>{
console.log(buttonRef)
buttonRef.current.remove()
}}>x</button>
</div>
)
}
const Button = React.forwardRef((props,ref)=>{
return (
<div ref={ref}>
{props.children}
</div>
)
})
const App =()=>{
const buttonRef = React.createRef(null)
return (
<div className = 'App'>
<Button ref={buttonRef}>按钮</Button>
<button className='close' onClick={()=>{
console.log(buttonRef)
buttonRef.current.x()
}}>x</button>
</div>
)
}
const Button = React.forwardRef((props,ref)=>{
const realButton = React.creatRef(null)
React.useImperativeHandle(ref,()=>{
return {
x : ()=>{
realButton.current.remove()
},
realButton:realButton
}
})
return (
<div ref={realButton}>
{props.children}
</div>
)
})
十一、自定义Hook
封装数据,通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
十二、Stale Closure
Stale Closure 过时闭包 函数引用的变量是之前的,而不是即时的。