【青训营】响应式系统与React

233 阅读2分钟

响应式系统

UI编程痛点

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

响应式编程

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

组件化

  1. 组件时组件的组合。
  2. 组件内拥有状态且对外不可见。
  3. 父组件可将状态传入组件内部。

React

一些设计思路

  1. React是单项数据流,永远是父组件给子组件传数据。如果子组件想改变父组件的状态,只需要父组件给子组件传一些函数,让子组件需要提示父组件状态改变时,通知父组件。

React的组件化

  1. 组件生命了状态和UI的映射。
  2. 组件拥有Props和State两种状态,分别来自于父组件和自身。
  3. 组件可以由其他组件拼接而成

React hooks的基本写法

接下来以一个简单的例子进行解释

// 导入React相关的对象和方法
import React, {useState, useEffect} from "react";

function App() {
  // useState用于获取和修改组件内的状态
  // 以下代码声明了状态变量count,初始值为0,使用setCount可对其进行修改
  const [count, setCount] = useState(0)
  // 用于执行副操作,除了状态相关的逻辑,比如网络请求,监听事件,查找DOM等
  useEffect(() => {
    document.title = `You click ${count} times`
  })
  // 返回JSX格式的数据
  return (
    <>
      <p>You click {count} times</p>
      <button onClick={() => setCount(count+1)}>点我加1</button>
    </>
  );
}
export default App;

React实现的问题

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

JSX的处理

JSX语法转义到JS。假如存在以下JSX代码:

import React from "react"
const Test = (props) => {
  const { url } = props
  return (
    <div className="root">
      <img src={url}  alt="图片"/>
    </div>
  )
}

通过React处理后,会生成以下JS代码:

import React from "react"
const Test = props => {
  const { url } = props
  return React.createElement("div", {
    className: "root"
  }, React.createElement("img", {
    src: url
  }))
}

JSX更新与DOM更新

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

主要方法就是当JSX所有更改后,使用diff算法,完成虚拟DOM的更改,然后将更改映射到真实DOM上。而完美的最小diff算法,需要O(n3)O(n^3)的复杂度,而React使用了一种启发式算法,其复杂度为O(n)O(n)。具体规则如下:

情况方法
不同类型的元素替换
同类型的DOM元素更新
同类型的组件元素递归

React状态管理库

核心思想: 将状态抽离到外部进行统一管理,避免只能父子组件间进行通信而造成效率低下和代码冗余的问题。

React的状态管理库都可以看成是一个状态机。状态机的特性是,能够从当前状态,在收到外部事件时,迁移到下一个状态。常用的管理库有redux、mobx、xstate、recoil。

Redux的简单使用

接下来,将结合原生Redux进行状态的操作。代码如下:

\*state.js*\
// 从redux中获取用于创建状态的变量
import { createStore } from 'redux'
// 创建函数,用于控制某个状态变量的初始化和相关事件
function countReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'add':
      return { value: state.value + 1 };
    default:
      return state
  }
}
// 根据传入的函数,创建一个状态变量,由于此处只涉及到一个变量,所以直接取名为state
const store = createStore(countReducer)
// 导出变量
export default store
import React, { useState, useEffect } from "react";
// 引入状态变量
import store from "./state";

function App() {
  // 借助了组件内的状态管理,将组件内的状态变量value和全局的状态变量value绑定在一起
  let [value, setValue] = useState(store.getState().value)
  useEffect(() => {
    document.title = `You click ${value} times`
  })
  // 订阅全局状态变量,用于监听全局变量的改变
  store.subscribe(() => {
    value = setValue(store.getState().value)
  });
  return (
    <>
      <p>You click {value} times</p>
      // 向全局状态变量指定相关事件
      <button onClick={() => store.dispatch({type: 'add'})}>点我加1</button>
    </>
  );
}

export default App;

其实这个操作还是比较复杂的,毕竟使用的是原生Redux,其实对于React来说,有专门适配React的react-redux,它简化了一些操作。感兴趣的读者可以研究研究。