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

82 阅读8分钟

前言

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天,前面我们讲了前端页面三剑客(HTML,CSS,Javascript)和网络相关的HTTP,这回我们来讲提升我们开发效率的前端框架React

前置知识

  1. HTML,JS,CSS基础
  2. 基础的数据结构/算法知识,如二叉树,深度遍历等
  3. 会使用浏览器提供的DOM API 来修改DOM,更新UI

React

将从以下几个点去介绍

    1. React 的历史与应用
    1. React 的设计思路
    1. React (hooks) 的写法
    1. React 实现
    1. React 状态管理库
    1. 应用级框架科普

React的历史应用

市面上应用的场景

    1. 前端应用开发,如 Facebook,Instagram, Netflix 网页版
    1. 移动原生应用开发,如 Instagram,DiscordOculus.
    1. 结合 Electron,进行桌面应用开发。
  • ...

React 发展历史
2010年
Facebook 在其 php 生态中,引入了 xhp 框架,首次引入了组合式组件的思想,启发了后来的 React 的设 计。
2011年
Jordan Walke 创造了 FaxJS, 也就是后来的 React 原型: 微信截图_20230202220832.png 2012年
在 Facebook 收购 nstagram 后,该 FaxJS 项目在内部得到使用,Jordan Walke 基于 FaxJS 的经验,创造了 React. 2013年
React 正式开源,在 2013 JSConf 上 Jordan Walke 介绍了这款全新的框架 微信截图_20230202221251.png 2014年-今天
生态大爆发,各种围绕 React 的新工具/新框架开始涌现: 微信截图_20230202221444.png

总结: 一个东西的产生就是解决了某些通点,有前提才有后续的发展。

React 的设计思路

当我点击不同型号或者不同的颜色改变相应的价格 下面就是通过原生JS 去实现的思路指示 Snipaste_2023-02-02_22-19-24.png

React 的设计思路-UI 编程痛点

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

React 的设计思路响应式与转换式

微信截图_20230202222609.png 响应式系统
事件 -> 执行既定回调 -> 状态变更

前端UI
事件 -> 执行既定回调 -> 状态变更 -> UI 更新

React 的设计与实现- 响应式编程

原来

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

期望

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

React 的设计与实现 - 组件化

image.png 微信截图_20230202223901.png 总结:

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

React 的设计与实现-状态归属问题

当前价格]状态属于谁 ?

正常都会想到是顶栏,型号,橱窗,甚至颜色;
但是你会发现这些状态是各个自我的共性的状态,这就需要一个人统一去管理,那就是Root节点;
但是都有一个Root 节点去管理的话,那么当数据量庞大时,也会进行臃肿
所以就有分层管理,自己的父节点管理属于自己的状态,最近祖宗节点管理父节点的状态,以此类推

当前价格 如何改变?

将 onChangeValue() 向下传递(Javascript中, 函数是[一等公民])

思考:

  1. React 是单向数据流,还是双向数据流
  2. 如何解决状态不合理上升的问题 ?
  3. 组件的状态改变后,如何更新 DOM ?
  1. React 是单项数据,永远是父组件传递子组件,只是子组件通过函数传递去将状态传递给父组件;
  2. 通过状态管理库进行
  3. 通过React 实现进行更新DOM

组件设计

  1. 组件声明了状态和 UI 的映射。
  2. 组件有 Props/State 两种状态
  3. “组件”可由其他组件拼装而成

映射关系: 我输入几个状态返回几个UI;
State: 内部私有状态 Props: 从外部传递过来的状态
组件拼装: 可通过拆分功能,合并功能去进行组件拼装

组件代码会是什么样子?
  1. 组件内部拥有私有状态 State。
  2. 组件接受外部的 Props 状态提供复用性
  3. 根据当前的 State/Props,返回一个 Ul。

可能长成以下的样子(单纯想象以下,不一定对)

function Component(props) {
  // props 是父组件的状态
  const { url } = props;
  this.text = '点击我'; // 状态
  
  // 返回一个'UI'
  return (<div>
      <SubComponent props={{ color: 'red' }}><SubComponent>
      <img src={url}></img>
      <button>text</button>
  </div>
}

React 的设计思路-生命周期

微信截图_20230203101537.png

并没有完全给出全部的生命周期,只是将突出的生命周期给了出来,挂载Mounting的时候,更新Updating的时候,卸载Unmounting的时候。

React (hooks) 的写法

import React,{ useState } from 'react';

function Example() {
    // Declare a new state varibale, which we'll call 'count'
    const [count, setCount] = useState(0)
    
    return (
        <div>
            <p>You click {count} times</p>
            <button onClick={() => setCount(count + 1) }>
              Click me 
            </button>
        </div>
    );
}

添加了状态管理库等插件,通过设置状态,返回元素并触发事件,更新UI

import React,{ useState } from 'react';

function Example() {
    // Declare a new state varibale, which we'll call 'count'
    const [count, setCount] = useState(0)
    
    // Similar to componentDidMount and componentDidUpdate;
    useEffect(() => {
       // Update the document title using the browser API
       document.title = `You clicked ${count} times`;
    })
    
    return (
        <div>
            <p>You click {count} times</p>
            <button onClick={() => setCount(count + 1) }>
              Click me 
            </button>
        </div>
    );
}

以上代码就比之前的代码添加了副作用的函数;
副作用: 一个组件内部单纯的只是执行一个语句,就是输入1+2,输出3,那就是一个纯函数;反之组件内部执行一个语句,影响外部
影响外部的例子:发起一个网络请求,和外部进行一个交互;给本地的session/location storage 去存储了一个字段; 上述代码中的改变了document.title,就是改变了外部系统的一个东西

Hook 使用法则

  1. 不要在循环,条件或嵌套函数中调用Hook.
// wrong
if (x > 1) {
    const [x, setX] = useState(0)
}
// Uncaught (in promise) ReferenceError: y is not defined

React 的实现

React 的实现 - Problems

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

React 的实现 - Problem1

compile

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

explore

"use strict"

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

React 的实现-Problem2

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

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

  • 声明式编程 指令式编程 响应式编程
    • 声明式编程:只需要告诉你需要干什么事件,不会告诉你步骤
    • 指令式编程:就是直接告诉你一个个步骤去完成某件事
    • 响应式编程:是声明式编程的一个类别,不仅可以声明UI,当某个状态发生改变的时候,就可以更改其他依赖的,它又可以自动更新UI(自己响应自己的过程)

为什么不直接将声明式语法植入浏览器系统里面呢?干嘛还需要引入React ReactDOM 包

浏览器作为应用平台,它自己不能提供高层的东西,如果将高层东西植入进去的,研发的自由度就会降低。 微信截图_20230203114207.png

React 的实现 -How to Diff?

更新次数少 <--TradeOff--> 计算速度快
完美的最小 Diff 算法,需要 O(n3)的复杂度
牺牲理论最小 Diff,换取时间,得到了 O(n)复杂度的算法

Heuristic O(n) Algorithm
微信截图_20230203114806.png

总结: React编译时和转译后的代码比较,通过改良DIff算法去优化DOM 和 Virtual DOM 的比较的效率,通过状态管理库进行管理数据状态,进行响应数据,更新UI

React 状态管理库

React 状态管理库 - 核心思想

微信截图_20230203143923.png 将状态抽离到UI外部进行统一管理

React 状态管理库 - 推荐

微信截图_20230203144216.png 将状态抽离到UI外部进行统一管理

React 状态管理库-状态机

微信截图_20230203144352.png 当前状态,收到外部事件,迁移到下一个状态

React 状态管理库 - Modern.js/Reduck

字节内部研发的状态管理库:modernjs.dev/

总结: 我们需要将需要复用的状态数据进行放进这个状态管理库,然后通过相关暴露出来的API进行做一些事,理解状态管理库实际在做什么事?运用场景?

应用级框架科普

    1. Next.js 硅谷明星创业公司 Vercel 的 React 开发框架,稳定,开发体验好,支持 Unbundled Dev,SWC等,其同样有 Serverless 一键部署平台帮助开发者快速完成部署。口号是“Let'sMakeWebFaster
    1. Modern.js 字节跳动 Web Infra 团队研发的全栈开发框架内置了很多开箱即用的能力与最佳实践,可以减少很多调研选择工具的时间。
    1. Blitz 无 API 思想的全栈开发框架,开发过程中无需写API 调用与 CRUD 逻辑,适合前后端紧密结合的小团队项目

Next.js 的功能 微信截图_20230203153153.png Modern.js的功能 微信截图_20230203153253.png

总结:前人裁树,后人乘凉;市面上已经有对我们开发应用比较好的工具已经非常的智能,快速,选项多了,合理使用这些工具,可让自己的开发效率变得比较高