响应式系统
UI编程痛点
- 状态更新,UI不会自动更新,需要手动调用DOM进行更新。
- 欠缺基本的代码层面的封装个隔离,代码层面没有组件化。
- UI之间的数据依赖关系,需要手动维护,如果依赖链路过长,容易造成回调地狱。
响应式编程
- 状态更新,UI自动更新。
- 前端代码组件化,可复用,可封装。
- 状态之间的互相依赖关系,只需要声明即可。
组件化
- 组件时组件的组合。
- 组件内拥有状态且对外不可见。
- 父组件可将状态传入组件内部。
React
一些设计思路
- React是单项数据流,永远是父组件给子组件传数据。如果子组件想改变父组件的状态,只需要父组件给子组件传一些函数,让子组件需要提示父组件状态改变时,通知父组件。
React的组件化
- 组件生命了状态和UI的映射。
- 组件拥有Props和State两种状态,分别来自于父组件和自身。
- 组件可以由其他组件拼接而成
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实现的问题
- JSX不符合JS标准。
- 返回的JSX发生改变时,如何更新DOM。
- 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算法,需要的复杂度,而React使用了一种启发式算法,其复杂度为。具体规则如下:
| 情况 | 方法 |
|---|---|
| 不同类型的元素 | 替换 |
| 同类型的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,它简化了一些操作。感兴趣的读者可以研究研究。