了解React| 青训营笔记

104 阅读4分钟

React的设计思路

UI编程的痛点

  • 状态更新,UI不会自动更新,需要手动调用DOM进行更新
  • 欠缺基本的代码层面的封装和隔离,代码层面没有组件化
  • UI之间的数据依赖关系,需要手动维护,如果依赖链路过长,则会遇到"Callback Hell"

响应式与转换式

  • 转换式系统:
    • 给定输入求解输出
    • 如编译器、数值计算
  • 响应式系统:
    • 监听事件,消息驱动
    • 如监控系统、UI界面

响应式系统

响应式系统流程:

  1. 事件发生
  2. 执行既定的回调函数
  3. 状态变更
    • 对于前端UI,需要再执行一步UI更新

React响应式编程解决的问题:

  1. 状态更新,UI自动更新
  2. 前端代码组件化,可复用,可封装
  3. 状态之间的互相依赖关系,只需声明即可

组件化

组件化准则:

  • 组件是组件的组合/原子组件
  • 组件内拥有状态,外部不可见
  • 父组件可以将状态传入内部组件(父组件可以控制内部组件)
    • 状态归属于两个节点时,向上寻找最近的共同的祖宗节点(状态上升)
    • 修改父组件的状态可以通过向下传递函数(JS中的函数可以视为变量),内部组件调用父组件的函数(单向数据流)

组件设计

  • 组件声明了状态和UI的映射
  • 组件有Props(外部接收状态)/State(私有状态)两种状态
  • 组件可由其他组件拼装而成

组件实现

  • 组件内部拥有私有状态State
  • 组件接受外部的Props状态,提供复用性
  • 根据当前的State/Props,返回一个UI

组件大致实现:

function Component(props) {
    const { url } = props;      // props状态
        this.text = 'click';    // state状态
        
        return (<div>
            <SubComponent props={{ color: 'red' }}></SubComponent>    // 作为父组件为内部组件传递props状态
            <img src={url}></img>
            <button>text</button>
        </div>)
}

生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载):已插入真实 DOM
  • Updating(更新):正在被重新渲染
  • Unmounting(卸载):已移出真实 DOM image.png

React(hooks)写法

hooks代码示例:

import React, { useState, useEffect } from 'react';

function Example() {
    // 使用react提供的useState函数(传入初始值)创建状态变量
    const [count, setCount] = useState(0);
    
    // 副作用:会对组件外部系统造成改变
    // 当函数存在副作用时,需要使用react提供的useEffect函数,useEffect只会在组件挂载的时候执行一次
    useEffect(() => {
        document.title = `You clicked {count} times`;
    });
    
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    )
}

hooks使用法则:不要在循环、条件或嵌套函数中调用hook

React实现

React存在的问题:

  • JSX不符合JS标准语法
  • 返回的JSX反生改变时,如何更新DOM
  • State/Props更新时,要重新触发render函数(组件函数自身)

JSX语法问题

通过语法转译,将标记语言片段转译为符合JS语法的React方法。

标记语言本质上是可以有属性的各结点组成的树状结构,可以转换为JS中的JSON或Object结构。 转换前:

const Test= (props) => {
    cosnt { url } = props;
    
    return (<div className="root"><img src={url} />
    </div>);
}

转换后:

const Test= (props) => {
    cosnt { url } = props;
    
    return /*#__PURE__*/React.createElement("div", {className: "root"}, 
                                            /*#__PURE__*/React.createElement("img", {src: url})
                                            );
}

DOM更新问题

真实DOM是浏览器内部的对象,不是JS中的对象,修改真实DOM的性能损耗较大。

Virtual DOM (虚拟DOM),是一种用于和真实DOM同步,而在JS内存中维护的一个对象,它具有和DOM类似的树状结构,并和DOM可以建立一一对应的关系。

利用虚拟DOM,React实现了声明式的API:React确保DOM匹配修改的UI状态。从而省去属性操作、事件处理和手动DOM更新这些在构建应用程序时必要的操作。

虚拟DOM运作原理:

  1. 在虚拟DOM上进行diff运算,得到需要更新的DOM结点 image.png
  2. 对于不同的区别类型采取不同的处理策略
    区别类型策略
    不同类型的元素替换
    同类型的DOM元素更新
    同类型的组件元素递归

React状态管理库

React状态管理库核心思想:将状态剥离到UI组件外部进行管理。

这种方式的弊端:会降低组件的复用性,组件会与状态管理库产生强耦合。

所以状态管理库一般应用于业务代码:

  • 对于组件拥有的状态,一般放在组件内部,提高复用性
  • 对于需要共享的状态,一般放在状态管理库中,方便共享
  • 状态管理库可以视为所有组件整体的状态

常用的状态管理库:

  • redux
  • xstate
  • mobx
  • recoil

状态机

状态机会接收外部事件,根据当前状态及外部事件类型,发生状态迁移/转换。 image.png

Modern.js/Reduck状态机示例:

import { model } from '@modern-js/runtime/model';

// 创建出来的model可以在程序中的任意位置使用,且操作的是同一个model对象及其状态
const countModel = model('count').define({
    // 状态机状态
    state: {
        x: 0,
        y: 0,
        sum: 0,
    },
    // 状态机事件
    actions: {
        incrementX(state) {
            state.x += 1;
        },
        incrementY(state) {
            state.y += 1;
        },
    }
})