本文是学习青训营课程《响应式系统与 React》与平时阅读博客和写项目的学习总结,用于帮助自己复习一遍。主要分为以下几点:
导航
- React 基础知识
- React 生命周期 与 hooks
- React 状态管理:redux
- React 框架伙伴:实用脚手架
React 基础知识
React不是mvvm[Vue是],它是单向数据绑定。
MVVM
- View:界面
- Model:数据模型
- ViewModel:作为桥梁负责沟通 View 和 Model
a. VirtualDOM是什么?
用于和真实DOM同步,而在JS内存中维护的一个对象,具有和DOM类似的树状结构,并和DOM可以建立一一对应的关系。实现了多次更新数据层的数据,最后异步处理只进行一次页面重绘。
JSX: 将原始 HTML 模板嵌入到 JS 代码中。必须使用Babel和webpack等工具将其转换为传统的JS
ReactElement将传入的几个属性进行组合,并返回。
type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class)key:组件的唯一标识,用于Diff算法ref:用于访问原生dom节点props:传入组件的propsowner:当前正在构建的Component所属的Component
ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM。
执行过程
React组件配合state创建一个虚拟DOM树- 根据虚拟
DOM树,生成一个真正的DOM树,再渲染到页面中 - 当
state或者props变化时,根据新的数据生成一个新的虚拟DOM树 - 将新旧虚拟
DOM树进行对比,通过diff算法找到新旧虚拟DOM的差异点,最后将差异点更新到页面上
b. Fiber是什么?
React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点后同步更新(O(n^3))。 React V16 版本中引入了 Fiber 机制。
分批延时对DOM进行操作,避免一次性操作大量 DOM 节点编译优化(JIT)及进行热代码优化,reflow 修正
Fiber 机制与线程并不一样,是一种让出CPU的机制。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
c. Diff算法是什么?
React需要同时维护两棵虚拟DOM树:
- 一棵表示当前的DOM结构
- 一棵在React状态变更将要重新渲染时生成
Trade Off: 更新次数少,计算速度快
牺牲理论换取时间 O(n): Tree Diff, Component Diff, Element Diff
- 不同类型的元素---> 替换;
- 同类型的DOM元素---> 更新;
- 同类型的组件元素 ---> 递归;
1. Tree Diff
按照树的层级进行比较,如果该节点不存在,则整个删除,不再继续比较。diff只简单考虑同层级的节点变化,如果跨层级就只会创建或删除节点
2. Component Diff
- 同一类型的两个组件 ----- 按层级比较虚拟DOM树
- 同一类型的两个组件 ----- 通过
shouldComponentUpdate()来判断是否需要判断计算 - 不同类型的组件 ----- 替换整个组件的所有节点
3. Element Diff
如果两个组件类型相同,则需要比较组件中的元素。diff提供三种节点操作:
(1)移动:同一层级的同组子节点元素发生变化,对同一层级的同组子节点添加唯一key进行区分,从而移动
(2)插入:组件 C 不在集合(A,B)中,需要插入
(3)删除: 组件 D 在集合(A,B,D)中,但 D的节点已经更改,不能复用和更新,则删除旧的D。组件D之前在集合(A,B,D)中,但集合变成新的集合(A,B)了,则需要删除D
key的作用: 为了在diff算法执行时更快的找到对应的节点,提高diff速度,更高效的更新虚拟DOM
d. 声明组件有哪几种方法?
- 函数式定义的
无状态组件 - ES5原生方式
React.createClass定义的组件RFC - ES6形式的
extends React.Component定义的组件RCC
有状态组件:类组件
无状态组件:函数组件
e. React 事件机制
- React 组件绑定事件不想要是事件冒泡的话应该调用
event.preventDefault()方法,而不是调用event.stopProppagation()方法。 - JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了
document上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
React 生命周期 与 hooks
namespace page {
export interface Props { } // props, fn
export interface State { } // state
}
class Page extends Component<Page.props, Page.State> {
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() { this.init() }
UNSAFE_componentWillRecieveProps(nextProps: Readonly<Page.props>) {}
componentWillUnmount() {}
init = function () {}
render() {
const {} = this.props
const {} = this.state
return ( <div>DEMO</div> )
}
}
异步请求,最好放在componentDidMount中去操作,对于同步的状态改变,可以放在componentWillMount中,一般用的比较少。constructor() -> componentWillMount() -> render() -> componentDidMount()
生命周期与 useEffect 对应
React.useEffect(() => {
// componentDidMount + componentDidUpdate
let timmer = setInterval(() => {
console.log('this is effect')
}, 1000)
return () => {
// componentWillUnmount
clearInterval(timmer)
}
}, [deps])
- 无依赖项,每次
render都会调用 [],页面加载时调用一次[deps],发生改变时执行,如监听登录状态
setState异步问题
做异步设计是为了性能优化、减少渲染次数
-
异步:比如在 React 生命周期事件和合成事件中,都会合并延迟更新
-
同步:在 React 无法控制的地方,比如原生事件的
addEventListener、setTimeout、setInterval中
React 状态管理
Redux:全局状态管理⼯具。解决 props 层级过深的问题。
- 可以用来处理副作用 side effect,例如异步操作
- 优势在于:可以串联和组合
connect,一个项目中使用多个中间件 - Redux 中间件用来处理状态更新,也就是在状态更新的过程中,执行一系列相应操作
Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个StoreState:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种数据集合就叫做StateAction:State的变化,必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种计算过程就叫做Reducer。它是一个函数,接受Action和当前State作为参数,返回一个新的State。dispatch:是View发出Action的唯一方法。
- 首先,用户(通过
View)发出Action,发出方式就用到了dispatch方法。 - 然后,
Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State State一旦有变化,Store就会调用监听函数,来更新View。
使用
- 创建
reducer- 可以使用单独的一个
reducer,也可以将多个reducer合并为一个reducer,即:combineReducers() action发出命令后将state放入reucer加工函数中,返回新的state,对state进行加工处理
- 可以使用单独的一个
- 创建
action- 需要发出多少动作就有多少指令
action是一个对象,使用type定义action类型
- 创建的
storestore可以理解为有多个加工机器的总工厂- 提供
subscribe,dispatch,getState这些方法
React Redux 将组件区分为 容器组件 和 UI 组件
Provider让所有组件都能够访问到Redux中的数据
<Provider store = {store}>
<App />
<Provider>
connect将Store作为props绑定到目标组件,mapStateToProps订阅Store更新[把Redux中的数据映射到组件的props中去],mapDispatchToProps改变Store[把各种dispatch也变成props]- 获取
state:connect通过context获取Provider中的store,通过store.getState()获取整个store tree上所有state - 包装原组件: 将
state和action通过props的方式传入到原组件内部
- 获取
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
中间件如何实现异步操作?
中间件执行时机:在 dispatching action 和到达 reducer 之间
dispatch(action) => [执行中间件代码] => reducer
原理: 封装了redux自己的dispatch方法。中间件修改了 store.dispatch,在分发动作和到达reducer之间提供了扩展。
// store/index.js
import { createStore, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const store = createStore(rootReducer, applyMiddleware(thunk, logger))
redux-logger中间件
作用:
- 记录日志
- 调用
store.dispatch()查看console中logger中间件的日志信息
const logger = store => next => action => {
console.log('prev state:', store.getState()) // 更新前的状态
// 记录日志代码
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState()) // 更新后的状态
return result
}
redux-thunk中间件
作用:
- 处理异步操作
- 可以处理函数形式的
action
const thunkAction = () => {
// dispatch函数用来分发 action
// getState函数用来获取 redux 状态
return (dispatch, getState) => {
// 执行异步操作
// 在异步操作成功后,可以继续分发对象形式的action 来更新状态
}
}
// action creator
const increment = payload => ({ type: 'counter/increment', payload })
dispatch(increment(2))
React 框架伙伴
好用,常用