响应式系统与React | 青训营笔记

137 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的第10天

响应式系统与React

一.React 的历史与应用

1.应用场景:

  • 前端应用开发,如Facebook,Instagram
  • 移动原生应用开发,如Instagram,Discord
  • 结合 Electron,进行桌面应用开发
  • React 语法书写3D图形应用,以及VR AR

2.历史:

  • 2010 年 Facebook 在其 php 生态中,引入了 xhp 框架,首次引入了组合式组件的思想,启发了后来的 React 的设计

  • 2011 年 Jordan Walke 创造了 FaxJS,也就是后来的 React 原型 image-20220808104730594.png

  • 2012 年 在 Facebook 收购 Instagram 后,该 FaxJS 项目在内部得到使用,Jordan Walke 基于 FaxJS 的经验,创造了 React

  • 2013 年 React 正式开源,在2013 JSConf 上 Jordan Walke 介绍了这款全选的框架

    有人反对,说这是一种倒退,在之前 Java 写网页也是用这种组合式来写的,所以说这是一种倒退

  • 2014 年 - 今天 生态大爆发,各种围绕 React 的新工具/新框架开始涌现

二.React 的设计思路

1.UI 编程的痛点

  • 购物案例,每单用户点击不同的产品参数时,UI上的价格也要同时变化,导致出现许多的 onClick 事件

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

image-20220808105906559.png

转换式系统:如编译系统,是一个 输入 - 求解 - 输出 的过程

前端代码:发生某个事件,就会发生一连续的变化,所以Javascript是异步编程.。 [事件 -> 执行既定的回调 -> 状态变更 -> UI变更]

响应式系统:如监控系统,在火灾发生时,触发报警响应等;如人烧开水,水开了我再关电,在这个等待水烧开期间,我们可以去干别的事,这就叫异步编程。 [事件 -> 执行既定的回调 -> 状态变更]

所以我们希望

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

2.组件化:

  • image-20220808110836271.png

    一个简单的结构,用树状表示出来,注意:这不叫DOM树,这仅仅是自己划分的

总结

  • 组件是 组件的组合/原子组件
  • 组件内拥有状态,外部不可见
  • 父组件可将状态传入组件内部

思考:【当前价格】 状态属于谁?

答:【当前价格】 属于 Root 节点!因为只有父组件才能控制子组件,当两个组件要共享一个状态时,要把状态上移,如果把状态都上移到 Root ,这就违背了组件复用的初衷了。大部分情况下,状态具有局部性,如颜色只需要要放在一个组件内。(伏笔:五.状态管理库)

结论:如果想要两个组件共享一个状态,则这个状态归属于两个节点向上寻找到最近的祖宗节点。

思考:【当前价格】怎么改变 ,它属于Root 节点,但触发改变时却属于 型号 和 颜色 节点。

答:将 onChangeValue()函数 从 Root 节点 向下传递。

思考:

  1. React 是单向数据流,还是双向数据流?
  2. 如何解决状态不合理上升的问题?
  3. 组件的状态改变后,如何更新 DOM?

答:

  1. 单向数据流,永远都是父组件往子组件传递,但父组件可以将一个函数传递给子组件,让子组件执行这个函数来改变父组件。
  2. 见后
  3. 见后

总结

  1. 组件声明了状态和 UI 的映射。(数学上的函数关系,输入什么状态,传出什么UI)
  2. 组件有 Props(内部私有) / State(外部传入) 两种状态。
  3. “组件”可由其他组件拼装而成。

思考:组件代码是什么样子?

答:

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

示例:image-20220808120425827.png

3.生命周期

image-20220808120708517.png

  1. 挂载到实际的DOM上,即 Mounting
  2. 状态发生改变时,重新执行 render 函数
  3. 更新到 DOM 上,即 React updates DOM and refs
  • 挂载 - Mounting
  • 卸载 - Unmounting
  • 状态改变时 - Updating

三.React (hooks)的写法

示例代码:

  1. import React, { useState } from 'react';
    ​
    function Example() {
        //声明一个新状态
        //useState传入初始值
        const [count, setCount] = useState(0); //count 状态本身,setCount 改变方式
        
        return (
            <div>
                <p> You clicked {count} times </p>
                <button onClick={()=> setCount(count + 1)}>
                    Click me
                </button>
            </div>
        )
    }
    
  2. import React, {useState, useEffect} from 'react';
    ​
    function Example() {
        count [count, setCount] = useState(0);
        
        //改变标题,副作用都用 useEffect ,都有个明确的执行时期
        useEffect(() => {
            docunment.title = `You clicked {count} times`;
        })
        
        return (
            <div>
                <p> You clicked {count} times </p>
                <button onClick={()=> setCount(count + 1)}>
                    Click me
                </button>
            </div>
        )
    }
    

    副作用:一个组件内单独执行一个语句,执行简单的语句不会影响外部,但如发起请求等就会影响到外部。

useState:

传入一个初始值,返回一个状态,和set该状态的函数,用户可以通过调用该函数,来实现状态的修改。

useEffect:

传入一个函数,和一个数组,数组是状态的数组,称作依赖项,该函数在 mount 时,和依赖性被 set 的时候会执行。

有“副作用”的函数,要传入 useEffect 来执行。副作用代表除了单纯的计算之外,还要做其它的一些事情,比如网络请求,更新DOM,localStorage存储数据等。

Hook 使用法则:

  1. 不要在循环条件或嵌套函数中调用 hook

四.React 的实现

1.Problems

  1. JSX 不符合 JS 标准语法
  2. 返回的 JSX 发生改变时,如何更新 DOM
  3. State/Props 更新时,要重新触发 render 函数

1.1Problem1

image-20220808230344309.png

通过转译

1.2Problem2

代码返回的类似于DOM片断,状态改变时DOM也改变。但是重新删除原来的DOM后,再添加新的DOM很消耗性能。

要实现diff的更新,能使DOM消耗低,但是diff的计算又不能太耗时。那么怎样平衡呢?

Virtual DOM (虚拟 DOM)

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

它赋予了 React 声明式的 API:您告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹配该状态。这使您可以从属性操作、事件处理和手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。

指令式编程:如C语言,一步一步告诉程序应该怎么做。如烧水,第一步装水,然后插电,接着...

声明式编程:告诉它,烧水

响应式编程:声明式的一个类别,但又可以根据某个状态改变时,又能自动更新UI等。

问:既然所有的前端框架都是声明式的,为什么不把声明式的能力接入到浏览器里?

答:作为应用平台,则不能提供太高层的内容,仅仅提供底层,毕竟如果有人就是喜欢指令式编程呢?

image-20220808231909035.png

性能问题:位置很高的父组件发生改变时,要影响的子组件过于多时,如何保证性能?

2.How to Diff ?

TeadeOff(权衡):

  • 更新次数少
  • 计算速度快

完美的最小 Diff 算法,想要)O(n^3)的复杂度。

牺牲理论最小 Diff,换取时间,得到O(n)复杂度的算法。

Heuristic O(n) Algorithm

元素种类实现方式
不同类型的元素替换
同类型的DOM元素更新
同类型的组件元素递归

五.React 状态管理库

1.核心思想

image-20220808233209439.png

降低复用性,组件内却依赖于外部的Store中

2.推荐

  1. redux

    • 用的较多
    • 最近的设计有些问题
  2. xstate

    • 状态集
  3. mobx

  4. recoil

将状态抽离到UI外部进行统一管理

3.状态机

image-20220808233727290.png

4.Modern.js/Reduck

image-20220808234107796.png

六.应用级框架科普

  1. NEXT.js

    • 硅谷明星创业公司Vercel的React开发框架,稳定,开发体验好,支持Unbundled Dev, SWC等,其同样有Serverless 一键部署平台帮助开发者快速完成部署。口号是"Let's Make Web Faster"
    • image-20220808234701542.png
  2. Modern.js

    • 字节跳动Web Infra团队研发的全栈开发框架,内置了很多开箱即用的能力与最佳实践,可以减少很多调研选择工具的时间。
    • image-20220808234713038.png
  3. Blitz

    • 无API思想的全栈开发框架,开发过程中无需写API调用与CRUD逻辑,适合前后端紧密结合的小团队项目。