引言
学了大半年Vue,现在来学习一下React,这是第一周的学习结果。写此文以做记录,便自己与他人学习。
为了在函数式组件中使用生命周期、state、store等,react引入了hooks的概念,通过hooks我们可以使用useEffect处理副作用,componentDidMount ,componentDidUpdate 、compentWillUnMount等生命周期,可以通过useState引入State数据等等。本人主攻Vue,闲时来学习学习React,此文作为笔记文杂糅了自己对常用hooks的理解,解释如有偏差请大家指正,阿累嘎多!!
常用Hooks
useEffect:
让函数式组件可以处理副作用(与UI渲染无关的一切代码),并且可以模拟componentDidMount,componentDidUpdate、compentWillUnMount
等生命周期。我的理解可以当做vue中的Mounted、Updated、beforeDestroy
来使用,useEffect是在渲染完毕后异步执行的,因此不会出现阻塞渲染的情况,但是会造成二次渲染的问题(useEffect中的副作用函数造成了第二次渲染)。useLayoutEffect:
useLayoutEffect与useEffect用法相似,主要区别是它是在渲染前(此时Dom已经根据VDom进行了修改,但是还没有渲染呈现到屏幕上)同步执行的
useInsertionEffect:
useInsertionEffect与useEffect用法相似,主要区别是它是在Dom修改前同步执行的
useState:
用于设置函数式组件的状态,在状态更新的时候(setXXX)会进行浅比较,当更新的状态值与当前状态值相同的则不会触发更新。useRef:
主要用于获取dom对象,获取子组件的实例,记录非状态数据用来持久化数据。useImperativeHandle:
用来绑定ref,配合useRef
一起使用,可以将子组件中的函数以及数据绑定在父组件传来的ref上。useReducer:
类似ReduxuseCallback:
用于在2次渲染间保存函数实例,防止多次渲染造成的函数重新创建,一般只在需要将函数作为 props 传递给子组件或将其作为依赖项传递给其他 Hook 时,才需要考虑使用useCallback
进行优化。否则,在大多数情况下,使用普通的函数定义即可。useMemo:
与vue中的computed相似,接受一个计算函数以及依赖项,只有当依赖项改变的时候才会重新执行计算函数,否则将用上次缓存的结果。useContext:
用起来和eventBus相似,用一个JS文件声明一个context,在祖父组件引入并用<ContextItemName.Context value={传递的值}>包裹子组件,在子组件中引入js文件,并用useContex('引入的Context实例')获取传递的值。useId:
用来获取唯一的Id,主要用来解决在C端和S端稳定的生成相同的唯一Id。useDeferredValue:
这个一开始听起来有点像是防抖,但是仔细一想却不是,它会推迟DeferredValue的造成的渲染,react首先先渲染其他非DeferredValue的造成的渲染,最后再渲染DeferredValue的造成的渲染,至于推迟多久全看电脑配置,电脑配置好的甚至感受不到推迟,可以用于一个数据改变造成渲染需要好几秒的情况。
Hooks的使用
useEffect
js
// 模拟 componentDidMount
useEffect(() => {
// 代码....
},[])
// 模拟componentDidUpdate
useEffect(() => {
// 代码....
})
// 模拟compentWillUnMount
useEffect(() => {
return () => {
// 代码....
}
},[])
useState
js
let [num,setNum] = useState(0)
setNum(n => n + 1)
setNum(n => n + 1)
setNum(n => n + 1)
setNum(n => n + 1)
console.log(num) // 4
let [num,setNum] = useState(0)
setNum(n+1)
setNum(n+1)
setNum(n+1)
setNum(n+1)
console.log(num) // 1
useCallback
js
// 只有当num改变的时候,计算函数才会重新计算,否则及时函数式组件重新渲染,也会只会给temp返回的上一次的缓存结果
let temp = useCallback(() => {
// 计算函数代码...
},[num])
useRef
js
// 绑定dom
function temp() {
let myRef = useRef()
return (
<div ref={myRef}></div>
)
}
//持久化非状态数据
let myRef = useRef('123')// 初始值123
console.log(myRef.current)// 当前值123
useImperativeHandle
js
function temp() {
let myRef = useRef()
return (
<Child ref={myRef}></Child>
)
}
function Child({props},ref) {
const useNameRef=useRef()
const useCodeRef=useRef()
// 通过useImperativeHandle将useNameRef、useCodeRef绑定到父组件传进来的ref上
useImperativeHandle(ref,()=>{
return {
useNameRef,
useCodeRef
}
return(
<div>
<input ref={useNameRef}></input>
<input ref={useCodeRef}></input>
</div>
)
}
useMemo
js
// 只有当num改变的时候,计算函数才会重新计算,否则及时函数式组件重新渲染,也会只会给temp返回的上一次的缓存结果
let temp = useCallback(() => {
// 计算函数代码...
},[num])
useContext
js
let myContext = React.CreateContext(defalutValue) // 注意在这个文件中是全局的,如果拆分开需要在父子页面中进行import引入。
function Parent() {
let [num,setNum] = useState(0)
return (
<myContext.Provider value={num}>
<Child1/>
<Child2/>
</myContext>
)
}
function Child1() {
let myCOntextValue = useContext(myCOntext) // 这里的myContext 是context实例,而不是字符串
....
}
function Child2() {
let myCOntextValue = useContext(myCOntext) // 这里的myContext 是context实例,而不是字符串
....
}
useId
js
// 第一次调用useId会返回一个:r0,第二次调用返回:r1 以此类推
let stdId = '001' + useId(); // 001:r0
一些Hooks的相关知识点
1.useEffect、useLayoutEffect、useInsertionEffect三者的区别?
- | 执行时机 | 执行机制 | 执行顺序 |
---|---|---|---|
useEffect | 渲染之后 | 异步(React18 后进行了批处理,都是异步) | 最后 |
useLayoutEffect | 渲染前(Dom已经根据VDom更新,但未渲染) | 同步 | 其次 |
useInsertionEffect | Dom修改前 | 同步 | 最先 |
2.为什么多次调用setNum(n+1)的时候会异常?
当num=0的时候,无论调用多少次setNum(n + 1),最后n的值也只会更新为2,因为这个操作是一个异步的,每次执行n+1时,这个n都是上一次切片。正确的解决方案是传递一个函数过去setNum(n => n + 1)。
js
let [num,setNum] = useState(0)
setNum(n + 1) // 此时切片中n为 0,执行 0 + 1
setNum(n + 1) // 此时切片中n为 0,执行 0 + 1
setNum(n + 1) // 此时切片中n为 0,执行 0 + 1
setNum(n + 1) // 此时切片中n为 0,执行 0 + 1
.... // 无论执行多少次都是0 + 1
这个与执行机制相关,react会先收集setNum的操作,最后集中处理,n的取值取上一次渲染时的切片结果直到下一次渲染。
3.使用Hooks应该要注意什么?
- 在函数式组件顶部使用
- 不要在循环语句或判断语句中使用
- 避免造成死循环,列如useEffect依赖于状态A,组件中又去修改状态A,造成无限死循环
- 注意获取状态的时候只是一个切片值,如题4
4.考验useRef和useState的熟练度,点击1次alter按钮后,3S内点击2次add按钮输出什么?
js
let [name,setName] = useState(0)
function tempHandler() {
// name 初始值为0
setTimeout(() => {
alert(name)
},3000)
}
return(
<div>
<button onClick={tempHandler} >alter</button>
<button onClick={() => {setName(n => n + 1)}}>add</button>
</div>
)
// 最后打印的是0
// 因为alter(name) 打印的是当时切片状态下的name状态,想打印最新的话,需要用useRef来解决,状态切片挺神奇的
5.React hooks解决了什么问题,为什么要使用Hooks?
- 复用状态逻辑
- 组件间的逻辑复用
- 简化组件,将相关逻辑进行分离,让组件代码更加清晰
- 更轻量的组件,避免实例化以及原型链带来的性能消耗
- 与函数式变成的结合,让React组件的逻辑更加纯粹以及可预测
6.useDeferredValue是防抖吗?和防抖有什么区别?
useDeferredValue并不能真的当做防抖来看待,它虽然会让改变的数据到其他数据处理完相关副作用后才处理其相关副作用,但是这个等待时间完全看使用者电脑的好坏,电脑性能好的话,甚至感觉不到延迟,电脑性能差的话较为明显,如果想感受这个延迟大家可以创建10000个div同时渲染一个DeferredValue数据,并用input来修改这个数据。
7.都有usestate来持久化保存数据了,为什么还需要用useRef来持久化数据呢?
react官网上说每次获取的state只是一个切片,useState的dispatcher调用过程是一个异步过程(setXXX 是一个异步操作)也就是你当时获取的状态值只是上一次更新的,而用useRef可以获取到最新的,并且useRef可以用来持久化非状态值,更新的时候也不会造成组件重新渲染。
总结
一周零零散散抽空学的东西,有些地方复盘的时候感觉还是理解的不到位,希望大家见谅并作出指正。