loading

react 相关知识点

# 为什么 hooks 不能写在循环或者条件判断语句里?

  • hooks 为了在函数组件中引入状态,维护了一个有序表。这样每次执行才能保证状态能对应上。
  • 比如第一次执行函数组件时,拿到状态 count(通过 setState,初始值为 0 )和 isDone(通过 setState,初始值为 false),它们其实被保存到一个有序表中,它们的值会记录下来: [0, true] 。
  • 第二次执行函数组件,setState 会 按顺序 从这个表中拿出 0 和 true,赋值给 count 和 isDone。
  • 如果把 hook 写到判断条件下,导致某个 setState 不执行了,假设 count 的 setState 因为判断条件没有执行,结果是 isDone 拿到了 0,发生了错位。
  • 函数本身不能保存状态,需要额外维护一个有序的表,在执行 setState 之类的 hook 时,将它们保存到这个表里。这要求每次函数组件的 hook 执行的位置相同,数量正确,否则会导致错位,不能拿到预期的状态值。

从源码的角度解释why

首先当这样写时

  const [name,setName] = useState('杜皮')
  const [address,setAddress] = useState('杭州')
1
2

每一个useState都会在当前组件中创建一个hook对象 ,并且这个对象中的next属性始终执行下一个useState的hook对象 这些对象以一种类似链表的形式存在 Fiber.memoizedState 中 而函数组件就是通过fiber这个数据结构来实现每次render后name address不会被useState重新初始化

正是因为hooks中是这样存储state的 所以只能在hooks的根作用域中使用useState,而不能在条件语句和循环中使用 因为我们不能每次都保证条件或循环语句都会执行

if (something) {
  const [state1] = useState(1)
}

// or

for (something) {
  const [state2] = useState(2)
}
1
2
3
4
5
6
7
8
9

fiber

每一个组件都会有一个fiber对象,在fiber中主要关注memoizedState这个对象,它就是调用完useState后对应的存储state的对象

调用useState后设置在memoizedState上的对象长这样:(又叫Hook对象)

{
  baseState,
  next,  
  baseUpdate,
  queue,
  memoizedState
}
1
2
3
4
5
6
7

这里面最需要关心的是memoizedState和next,memoizedState是用来记录这个useState应该返回的结果的,而next指向的是下一次useState对应的`Hook对象,即

hook1  ==>	Fiber.memoizedState
state1 === hook1.memoizedState
hook1.next	==>	hook2
state2	==>	hook2.memoizedState
....
1
2
3
4
5

# 常用的Hooks

useEffct使用:
如果不传参数:相当于render之后就会执行
传参数为空数组:相当于componentDidMount
如果传数组:相当于componentDidUpdate
如果里面返回:相当于componentWillUnmount
会在组件卸载的时候执行清除操作。
effect 在每次渲染的时候都会执行。
React 会在执行当前 effect 之前对上一个 effect 进行清除。
 
useLayoutEffect:
useLayoutEffect在浏览器渲染前执行
useEffect在浏览器渲染之后执行
 
当父组件引入子组件以及在更新某一个值的状态的时候,往往会造成一些不必要的浪费,
而useMemo和useCallback的出现就是为了减少这种浪费,提高组件的性能,
不同点是:useMemo返回的是一个缓存的值,即memoized 值,而useCallback返回的是一个memoized 回调函数。
 
 
useCallback
父组件更新子组件会渲染,针对方法不重复执行,包装函数返回函数;
 
useMemo:
const memoizedValue =useMemo(callback,array)
callback是一个函数用于处理逻辑
array 控制useMemo重新执⾏行的数组,array改变时才会 重新执行useMemo
不传数组,每次更新都会重新计算
空数组,只会计算一次
依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个 useMemo 返回的值)
不能在useMemo⾥面写副作⽤逻辑处理,副作用的逻辑处理放在 useEffect内进行处理
 
自定义hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook,
自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性
在我看来,自定义hook就是把一块业务逻辑单独拿出去写。
 
 const [counter, setCounter] = useState(0);
 const counterRef = useRef(counter);  // 可以保存上一次的变量
 
useRef 获取节点
function App() {
    const inputRef = useRef(null);
 
    return <div>
        <input type="text" ref={inputRef}/>
        <button onClick={() => inputRef.current.focus()}>focus</button>
    </div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# React事件机制

<div onClick={this.handleClick.bind(this)}>点我</div>
1

React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。

除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件。.因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。

# 受控组件和非受控组件

受控组件:通过setState的形式控制输入的值及更新

非受控组件:通过dom的形式更新值,获取值通过ref的形式去获取

# React组件生命周期

  • constructor:在构造函数中初始化props和state
  • componentWillMount:组件渲染之前执行,对state进行最后修改
  • render:渲染
  • componentDidMount: 组件渲染之后执行
  • componentWillReceiveProps:这个周期函数作用于特定的 prop 改变导致的 state 转换
  • shouldComponentUpdate:用来做性能优化,根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。
  • componentWillUpdate:数据在改变之前执行
  • componentDidUpdate:渲染发生后立即调用
  • componentWillUnmount:从 DOM 卸载组件后调用。用于清理内存空间

react17 会删除以下三个生命周期 componentWillMount,componentWillReceiveProps , componentWillUpdate

# 调用setState之后发生了什么

setState会进行状态更新

将传入的参数对象与组件当前状态合并,然后触发所谓的调和过程,经过调和过程,根据新的state,React元素会重新构建虚拟DOM,进行diff算法对比新旧虚拟DOM树的区别,进行视图更新,而不是全部渲染

setState 采用的任务队列机制,不会马上执行,而是加入队列,在下次事件循环是一次性执行

# 为什么建议传递给setState的参数是一个callback(回调函数)而不是一个对象

this.props和this.state的更新可能是异步的,不能依赖他们的值去计算下一个state

# setState第二个参数的作用

该函数会在setState函数调用完成并且组件开始重渲染的时候被调用,可以用该函数来监听渲染是否完成

最近更新
01
2023/07/03 00:00:00
02
2023/04/22 00:00:00
03
2023/02/16 00:00:00