React学习笔记

99 阅读4分钟
import React from 'react'
import ReactDOM from 'react-dom'

// React.createElement(
//   type,
//   [props],
//   [...children]
// )

fiber对应关系

child、return、sibling

React.Children.toArray(children) 扁平化规范化react数组


const flatChildren = React.Children.toArray(children)

const newChildren = []
React.Children.forEach(flatChildren, (item) => {
    if (React.isValidElement(item)) newChildren.push(item)
})


const lastChildren = React.createElement(`div`, { className: 'last'}, `say goodbye`)
newChildren.push(lastChildren)

const newReactElement = React.cloneElement(reactElement, {}, ...newChildren)
  • @babel/plugin-syntax-jsx 和 @babel/plugin-transform-react-jsx

  • jsx语法实现来源于这两个babel插件:

  • @bable/plugin-syntax-jsx:能够能babel有效的解析jsx语法

  • @bable/plugin-transform-react-jsx:内部调用了@babel/plugin-syntax-jsx,可以把React JSX转化成JS能够识别的createElement格式

  • 我们写的jsx会先转换成React.createElement,然后再转化成React.fiber的过程。

  • 在React调和渲染fiber节点的时候,如果发现fiber tag是ClassComponent = 1,则按照类组件逻辑处理,如果是FunctionComponent = 0, 则按照函数组件逻辑处理

class Index extends React.Component {
  constructor (...arg) {
    super(...arg)
  }
  state = { a: 1 }
  static number = 1
  handleClick = () => {
    console.log(111)
    console.log(this)
  }
  componentDidMount () {
    console.log(Index.number, Index.number1)
  }
  render () {
    return <div onClick={this.handleClick}>hello,world</div>
  }
}
Index.number1 = 2
Index.prototype.handleClick = () => console.log(222)

console.dir(Index)
function Index () {
  console.log(Index.number)
  const [message, setMessage] = React.useState('hello, world')
  return <div onClick={() => setMessage('let us learn React')}>{message}</div>
}
Index.number = 1
对于类组件来说,底层只需要实例化一次,实例中保存了组件的state等状态。对于每一次更新只需要调用render方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。
一次事件中触发一次setState,在React底层主要做了哪些事?
  1. 首先,setState会产生当前更新的优先级
  2. 接下来React会从fiber Root根部fiber向下调和子节点,调和阶段将对比发生更新的地方,找到发生更新的组件,合并state,然后触发render函数,得到最新的UI,完成render阶段
  3. 接下来到commit阶段,commit阶段,替换真是dom,完成此次更新流程
  4. 此时仍然在commit阶段,会执行setState中的callback函数,到此为止完成了一次setState全过程
类组件初始化过程中绑定了负责更新的updater对象,对于如果调用setState方法,实际上是React底层调用Updater对象上的enqueueSetState方法
enqueueSetState的作用,就是创建一个update,然后放入当前fiber对象的待更新队列中,最后开启调度更新。
React同一级别更新优先级关系是:
  • flushSync中的setState > 正常执行上下文中setState > setTimeout,Promise中的setState.
function Index2 (props) {
  const [number, setNumber] = React.useState(0)
  React.useEffect(() => {
    console.log('监听number变化,此时的number是:'+ number)
  }, [number])
  const handleClick = () => {
    setTimeout(() => {
      setNumber(3)
      console.log('number3', number)
    })
    setNumber(1)
    console.log('number1', number)
    ReactDOM.flushSync(() => {
      setNumber(2)
      console.log('number2', number)
    })
    
    
  }
  console.log(number)
  return <div>
    <span>{number}</span>
    <button onClick={handleClick}>number++</button>
  </div>
}
类组件的setState和函数组件的useState有什么异同?
  • 首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。

不同点

  1. 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
  2. setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
  3. setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。
schedulUpdateOnFiber
schedulUpdateOnFiber
pureComponent
shouldComponentUpdate
class组件生命周期

在mount阶段,首先执行的constructorClassInstance函数,用来实例化React组件,在实例化组件之后,会调用mountClassInstance组件初始化。

  1. 组件初始化阶段

constructor getDerivedStateFromProps:是从类上直接绑定的静态方法,传入props、state,返回值将和之前的state合并,作为新的state,传递给组件实例调用。 render componentDidMount

  1. 更新阶段

getDerivedStateFromProps(nextProps, prevState):用于在初始化和更新阶段,接受父组件的props数据,可以对props进行格式化,过滤等操作,返回值将作为新的state合并到state中,供给视图渲染层消费。 shouldComponentUpdate(newProps, newState, nextContext) shouldComponentUpdate(newProps, newState, nextContext) render getSnapshotBeforeUpdate(prevProps, prevState) componentDidUpdate(prevProps, prevState, snapshot)

  1. 销毁阶段

componentWillUnmount

useEffect执行,React处理逻辑是采用异步调用,对于每一个effect的callback,React会像setTimeout回调函数一样,放入任务队列,等到主线程任务完成,DOM更新,js执行完成,视图绘制完毕,才执行,所以useEffect回调函数不会阻塞浏览器绘制视图。
useLayoutEffect是在DOM更新之后,浏览器绘制之前,这样可以方便修改DOM,获取DOM信息,这也浏览器只会绘制一次,如果修改DOM布局放在useEffect,那useEffect执行是在浏览器绘制视图之后,接下来又修改DOM,就可能会导致浏览器再次回流和重绘。而且由于两次绘制,视图上可能会造成闪现突兀的效果。
useLayoutEffect callback中代码执行会阻塞浏览器绘制。
useInsertionEffect:执行的时候,DOM还没有更新,主要是解决CSS-in-JS 在渲染中注入样式的性能问题,所以在useInsertionEffect使用CSS-in-JS避免了浏览器出现再次重回和重排的可能,解决了性能的问题。