尼采般地抒情

尼采般地抒情

尼采般地抒情

音乐盒

站点信息

文章总数目: 316
已运行时间: 1570

虚拟DOM以及React挂载DOM的方式

  1. 利用JavaScript脚本操作DOM的行为是性能损耗的大头,故产生虚拟DOM的概念。初始化在内存中得到相应的虚拟DOM树,然后将该结果一次性作用于真实DOM,逻辑层的变动导致部分视图层的改变这渲染逻辑也是经过虚拟DOM加上diff得到一次性结果然后作用于真实DOM,在这里Vue和React都是一样的。
  2. 可以简单理解:react模块旨在完成虚拟dom的相关逻辑,react-dom模块旨在完成真实DOM和diff的处理
// mount container
rootReactDOM.createRoot(document.querySelector('#root'))
// create VDOM
const data = React.createElement('div', {}, 'data')
// VDOM -> true DOM
root.render(data)

JSX

上述ReactDOM生成虚拟DOM的方式,实际编写代码不方便,于是出现了类似JSX来简化视图层的代码编写(Vue中利用Template模板来完成相同视图层的逻辑构建)

  1. JSX的识别要么在jsx后缀文件书写相关代码逻辑,要么利用babel工具来进行代码转义..
const data = (
  <div>
    content<span>something...</span>
  </div>
)
root.render(data)
  1. 模板语法:{},Vue是是{{}}
    1. 注释:{/* */}
    2. 内容可以变量、函数、JSX嵌套 ······
  1. JSX注意事项
    1. 严格单标签(<Tab />
    2. 标签名小写
    3. 唯一根节点 -> <></>React.Fragment
    4. 部分HTML属性名变动:class -> className; for -> htmlFor ······
    5. 多单词属性用驼峰方式书写,dataset除外

理解类组件

  1. 组件的概念也就是模块的拆分、抽象和复用,和Vue组件一致,React的组件编写有两种,类组件函数组件,实际编写代码,React18函数式组件编写更为推崇,但是理解上以类组件会更好。
  2. 具体类组件的编写,其实就是在面向对象编程,给你一个已知类React.Component,写具体逻辑时,遵照所继承的类的一些规则来扩展业务代码。

简单的父子通信

// A component
class A extends React.Component {
  constructor(props) {
    super(props)
    this.props = props
  }
  render {
    this.props.getData('data')
    return (
      <div>A component {this.props.info} </div>
    )
  }
}

// B component
const getData = (data) => {
  // result -> data
}
const data = <div>content <A info='something' getData={getData} /> </div>

props相关事项

  1. 传入组件的参数可以用扩展运算符来传入
const data = {
  info: 'something,
  getData: (data) => {},
}
const data = <div><A {...data} /></div>
  1. 单独属性值为true
  2. 类组件的默认值
// A component
class A extends React.Component {
  static defaultProps = {
    info: 'default info',
  }
  constructor(props) {
    super(props)
    this.props = props
  }
  render {
    this.props.getData('data')
    return (
      <div>A component {this.props.info} </div>
    )
  }
}

响应式变量

前端框架必须具体的一个能力就是响应式变量的构造,以及内部实现的动态渲染,react的响应式变量就是构造类的一个私有变量state,再利用其规定指定的方法setState方法来实现响应式

class A extends React.Component {
  state {
    a: 1,
  }
  static defaultProps = {
    info: 'default info',
  }
  constructor(props) {
    super(props)
    this.props = props
  }
  render {
    this.props.getData('data')
    return (
      <div>A component {this.props.info} </div>
    )
  }
	handleMethod() {
    this.setState({ a: 2 })
	}
}

如果响应式的值为数组呢?

  1. react里面的响应式的值为不可变数据集合,比较简单的情况可以利用剩余参数来解决该问题。
class A extends React.Component {
  state ={
    a: [1, 2, 3]
  }
  ···
  handleMethod() {
    this.setState({ a: [...a, 4] })
  }
}
  1. 深拷贝

(TODO: 应该有更好的解决方式)

批处理

响应式处理,内部会一次性收集当前变化的量,进入一个处理队列,最终得到一个最终态来进行渲染,避免状态改变多次渲染的情况。

  1. React18之前,在一些函数执行时机下,不会进行批处理,React18之后解决了这个问题。具体有:Promise、setTimeout、原生事件下
  2. React提供了防止批处理的函数
class A extends React.Component {
  state ={
    a: 1
  }
  ···
  handleMethod() {
    ReactDOM.flushSync(() => {
      this.setState({ a: this.state.a + 1 })
      this.setState({ a: this.state.a + 1 })
    })
    // result: run twice render method...
  }
  render {
    console.info('react render.')
    return (
      <div>A component {this.props.info} </div>
    )
  }
}
  1. setState是一个一个异步函数,其第二个参数可以传入回调函数,以便写状态改变之后的相关业务逻辑
  2. 如果传入setState的值是对象,那么会在响应式内部所收集变化的量里面进行覆盖,如果需要实时根据最新变化的量做逻辑处理,则需要传入一个参数为state的回调函数
class A extends React.Component {
  state ={
    a: 1
  }
  ···
  handleMethod() {
    // 1. cover state
    this.setState({ a: this.state.a + 1 })
    this.setState({ a: this.state.a + 1 })
    // result: a -> 2

    // 2. real time state
    this.setState((state) => { a: this.state.a + 1 })
    this.setState((state) => { a: this.state.a + 1 })
    // result: a -> 3
  }
}

PureComponent shouldComponentUpdate

下面代码,因响应式变量变化,所以会执行一次render渲染

class A extends React.Component {
  state ={
    a: 1
  }
  ···
  handleMethod() {
    this.setState({ a: 1 })
  }
  render {
    console.info('react render.')
    return (
      <div>A component {this.props.info} </div>
    )
  }
}

上面情况其实响应式的结果都是a变为1,所以从渲染结果上看其实可以优化掉该种情况的渲染,react提供的渲染优化方式有两种:

  1. PureComponent优化组件
class A extends React.PureComponent {
  ···
}
  1. shouldComponentUpdate生命周期函数
class A extends React.Component {
  state ={
    a: 1
  }
  ···
  shouldComponentUpdate = (nextProps, nextState) => {
    if (nextState.a === this.state.a)
    	return false // not render
    return true // run render
  }
  render {
    console.info('react render.')
    return (
      <div>A component {this.props.info} </div>
    )
  }
}

Refs

框架只是对渲染dom的前置操作做了一层封装,比如先构造虚拟dom或是渲染时机的优化等,但有些时候需要在业务代码里面直接获取dom进行操作,比如input标签的自动聚焦(focus)

  1. 变量
// A component
class A extends React.Component {
  inputRef = React.createRef()
  // this.inputRef.current // -> Input DOM
  render {
    return (
      <div>
      	A component
        <input ref={inputRef} />
      </div>
    )
  }
}
  1. 回调函数
// A component
class A extends React.Component {
  inputRef = (dom) => {
    dom.focus()
  }
  // this.inputRef.current // -> Input DOM
  render {
    return (
      <div>
      	A component
        <input ref={inputRef} />
      </div>
    )
  }
}
  1. 除了可以得到DOM,也可以得到类组件的实例对象
// A component
class A extends React.Component {
	···
}

// B component
class B extends React.Component {
	refData = React.createRef() 
  render() {
    return <div>content <A ref={refData} /> </div>
  }
}

生命周期

组件模式:RenderProps

class A extends React.Component {
  state = {
    x: 0,
    y: 0,
  }
  constructor(props) {
    super(props)
    this.props = props
  }
  componentDidMount = () => {
    document.addEventListener('mousemove', this.move)
  }
  componentWillUnMount = () => {
    document.removeEventListener('mousemove', this.move)
  }
  move = (e) => {
    this.setState({
      x: e.pageX,
      y: e.pageY,
    })
  }
	render() {
    return (
      <React.Fragment>
      	{ this.props.render(this.state.x, this.state.y)}
      </React.Fragment>
    )
  }
}
class B extends React.Component {
	render() {
    return <A render={(x, y) => {
      <div>{x}, {y}</div>
    }} />
  }
}

组件模式:HOC

参数为组件,返回新组件

function hocFun(WithComponent) {
  return class extends React.Component {
    state = {
      x: 0,
      y: 0,
    }
    componentDidMount = () => {
      document.addEventListener('mousemove', this.move)
    }
    componentWillUnMount = () => {
      document.removeEventListener('mousemove', this.move)
    }
    move = (e) => {
      this.setState({
        x: e.pageX,
        y: e.pageY,
      })
    }
      render() {
      return <WithComponent {...this.state} />
    }
  }
}
class A extends React.Component {
  render() {
    return (
      <div>
          {this.props.x}, {this.props.y}
      </div>
    )
  }
}
const C = hocFun(A)
class B extends React.Component {
    render() {
    return <C />
  }
}

Context通信

const ContextData = React.createContext()

class A extends React.Component {
state = {
info: ‘something’
}
render() {
return (
A
<ContextData.Provider value={this.state.info}>
<B />
</ContextData.Provider>
)
}
}
class B extends React.Component {
render() {
return (
B<C />
)
}
}
class C extends React.Component {
static contextType = ContextData
componentDidMount = () => {
console.lof(this.context)
}
render() {
return (
C
<ContextData.Consumer>{ value => value }</ContextData.Consumer>
)
}
}
// render -> ABCsomething

函数组件

useState

响应式变量的声明和改变

import { useState } from 'React'
const A = () => {
  const [a, setA] = useState('a')
  return <div>a value: {a}</div>
}

和类组件的差异

  1. 改变值的时候不会merge,而是覆盖
  2. 初始值可以值回调函数,但不能是函数否则每次改变都会重新执行一遍该函数
  3. 同样存在批处理,也有防止批处理的函数
import { useState } from 'React'
import { flushSync } from 'ReactDDOM'
const A = () => {
  const [a, setA] = useState('a')
  const [b, setB] = useState('b')
  flushSync(() => {
    setA('aa')
  })
    flushSync(() => {
    setB('bb')
  })
  return <div>a value: {a}</div>
}
  1. 传入的是值,渲染前会存在覆盖情况,如果传入的是函数,则会保留上一次的状态值计算

useEffect

副作用Hook

  1. 模拟各个生命周期的执行时机
import { useState, useEffect } from 'React'
const A = () => {
  useEffect(() => {
    // mount || update
    return () => {
      // beforeUpdate || unMount
      // clear sider function...
    }
  })
  return <div>a value: {a}</div>
}
  1. 关于第二个数组参数
    1. 如果不设置,那么在update时期都会执行
    2. 如果设置空数组(且回调函数中没有响应式变量),则只在mount时期执行一次
    3. 如果useEffect回调函数中存在响应式变量,那么第二个参数数组中应当有该响应式变量
  1. useLayoutEffect
    1. useEffect:渲染并绘制到屏幕之后执行,异步
    2. useLayoutEffect:渲染之后,但绘制到屏幕之前执行,同步
    3. 一般如果回调函数中有DOM的相关操作并且会改变样式,用后者,避免DOM渲染闪屏/白屏,但前者性能方面更好

useRef

  1. 作用在DOM上的回调函数,则为DOM实例
const A = () => {
  const elementFun = (dom) => {
    // dom -> input DOM
  }
  return <div><input type="text" ref={elementFun} /></div>
}
  1. 作用在DOM上且为useRef函数,则为带有键为current值为DOM本身的对象
import { useRef } from 'React'
const A = () => {
  const obj = useRef()
  // obj => { current: inputDOM }
  return <div><input type="text" ref={obj} /></div>
}
  1. 不可以作用在组件上,但是子组件有React.forwardRef(组件转发)可以
import { useRef } from 'React'
const Son = React.forwardRef((props, ref) => {
  return <div><input type="text" ref={ref} /></div>
})
const A = () => {
  const obj = useRef()
  // obj => { current: inputDOM }
  return <Son ref={obj} />
}
  1. 利用useRef创建的普通变量,可以具备“记忆”功能,类似于类的实例属性。利用这一点可以设置一个flag来实现组件仅更新时触发的代码执行时机
import { useRef, useEffect } from 'React'
const A = () => {
  const obj = useRef(false)
  const inputOnClick = () => {
    obj.current = true
  }
  useEffect(() => {
    if (obj.current) {
      // only update -> run...
    }
  })
  return <div><input type="text" onClick={inputOnClick} /></div>
}

useContext

跨组件通信

import { useContext, createContext } from 'React'

// not Provider value -> render default value
const C = createContext(‘default value…’)

const GSon = () => {
const value = useContext(C)
// value -> something…
return <div>GSon template…</div>
}
const Son = () => {
return <GSon />
}

const A = () => {

return (
<C.Provider value=’something…’>
<Son ref={obj} />
</C.Provider>
)
}

memo

类似类组件中的PureComponent性能优化组件

  1. 函数组件中当响应变量的值没有发生改变,不会重新渲染,和类组件不一样
  2. 当组件的值发生改变才进行render,反之不进行render
import { useState, memo } from 'React'


const Son = memo(() => {
return <div>son</div>
})

// continue click -> continue run ↓
// const Son = () => {
// return <div>son</div>
// }

const A = () => {
const [count, setCount] = useState(0)
const clickEvent = () => {
setCount(1)
}
return (
<div>
<button onClick={clickEvent} >click</button>
<Son count={count} />
</div>
)
}

useMemo和useCallback

React函数式组件在重新渲染时候,代码执行会将函数体重新执行,即便结果不是重新更新DOM。如果函数体的代码逻辑复杂会带来不小的性能损耗,如果能够对这过程中的部分“量”进行“记忆”,则会较大提升性能。

响应式变量内部会自动进行记忆,但是如果是非响应式变量,比如一个对象或是一个函数传入组件,这其实也是改变了的,原因是对象的引用改变,依然会引起DOM的重新渲染,而利用useMemo或是useCallback创建的对象(对象、数组、函数)则会保留记忆功能。

  1. useCallback:传入回调函数
  2. useMemo:传入一个必须带有返回值的函数
  3. 两者的第二个参数都是依赖数组,同useEffect
import { useCallback, useMemo } from 'React'
const A = () => {
  // 1. every render -> run
  const fun = () => {}
  // 2. useCallback (ignore const error...)
  const fun = useCallback(() => {}, [])
  // 3. useMemo (ignore const error...)
  const fun = useMemo(() => () => {}, [])
  const a = useMemo(() => [1, 2, 3], [])
  

return <div onClick={fun} a={a}></div>
}

useReducer

管理多个有关联的响应式变量

import { useReducer } from 'React

const loginState = { isLogin: true, isLogout: false }
const loginReducer = (state, action) => {
switch(action.type) {
case ‘login’:
return { isLogin: true, isLogout: false }
case ‘logout’:
return { isLogin: false, isLogout: true }
default:
return new Error()
}
}

const A = () => {
const [state, LoginDispatch] = useReducer(loginReducer, loginState)
const clickEvent = () => {
loginDispatch({ type: state.isLogin ? ‘logout’ : ‘login’ })
}

return (
<button onClick={clickEvent}>{state.isLogin ? ‘login’, ‘logout’}</button>
)
}

并发模式

  1. React18之前,渲染是一个单一的、不间断的、同步的事务,一旦渲染开始,就不能被中断
  2. React18引入并发模式,它允许你将更新作为一个transitions,这会告诉React他们可以被中断执行。这样可以把紧急的任务先更新你,不紧急的任务后更新

startTransition

import { startTransition } from 'React'
const A = () => {
  ...
  const fun = () => {
    // 紧急任务
    setA('')
    // 不紧急任务(将内部的任何非紧急状态更新标记为 Transition)
    startTransition(() => setB(''))
  }
  return <div></div>
}

useTransition和useDeferredValue

  1. useTransition返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数
import { useTransition } from 'React'
const A = () => {
  ...
  const [pending, startTransition] = useTransition()
  const fun = () => {
    // 紧急任务
    setA('')
    // 不紧急任务(将内部的任何非紧急状态更新标记为 Transition)
    startTransition(() => setB(''))
  }
  return <div></div>
}
  1. useDeferredValue接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后
import { useDeferredValue, useState } from 'React'
const A = () => {
  const [a, setA] = useState('')
  // aD => 不紧急时候的值(同a),也就是延迟之后的值
  const aD = useDeferredValue(a)
  ...
  return <div></div>
}

自定义hook

  1. 命名通常使用use开头
import { useState, useEffect } from 'React'

const useMouseXY = () => {
const [x, setX] = usestate(0)
const [y, setY] = usestate(0)

useEffect(() => {
function move(e) {
setX(e.pageX)
setY(e.pageY)
}
document.addEventListener(‘mousemove’, move)
return () => document.removeEventListener(‘mousemove’, move)
}, [])

return { x, y }
}

// use
const { x, y } = useMouseXY()

lazy

当模块化引入(import)组件,但在实际代码中未使用到,代码内部逻辑仍会执行,这个时候可以利用lazy使组件异步化加载,从而达到性能优化效果

import { lazy } from 'react';

const AuthRoute = lazy(() => import(‘./AuthRoute’));
const ErrorPage = lazy(() => import(‘@/components/ErrorBoundary’));
const NotFound = lazy(() => import(‘@/pages/404’));

Suspense

组件加载中或是切换过程中的“中间态”

import { Suspense, useState } from 'react';
import { RouterProvider } from 'react-router-dom';
import router from '@/router/index';
import Loading from '@/components/Loading';

function MyApp() {
const [show, setShow] = useState(true)
const clickEvent = () => setShow(!show)
return (
<>
<button onClick={clickEvent}>btn</button>
<Suspense fallback={<Loading />}>
{show ? <RouterProvider router={router} /> : <>other template</>}
</Suspense>
</>
);
}

export default MyApp;

结合startTransition使用:

如果需要切换过程中不展示loading的加载态,则将上述代码中的切换逻辑转为并发异步即可

const clickEvent = () => startTransition(() => setShow(!show))

错误边界捕获

如果编写的组件内部报错,那么react渲染会清除根节点DOM,React官方文档暂时只有类组件编写的示例代码。其核心逻辑在类组件上支持关键的生命周期方法getDerivedStateFromError()和componentDidCatch(),无法将错误边界编写为函数式组件,同时以下几种错误情况,也捕获不到:

  1. 异步代码
  2. 事件处理函数
  3. 服务器组件
  4. Error Boundary自身

具体的完整封装 TODO:

ReactDOM

createPortal

  1. 可以指定节点挂载到指定目标DOM节点
// template
const A = () => ReactDOM.createPortal(<div>info</div>, document.body)
  1. 对于一些全局组件(message),DOM的挂在可能不在root根节点内,自定义挂在指定DOM节点,除了上述createPortal方式,还有react的createRoot方式

评论区