React基础学习笔记与应用 | 青训营

61 阅读8分钟

本文是学习青训营课程《响应式系统与 React》与平时阅读博客和写项目的学习总结,用于帮助自己复习一遍。主要分为以下几点:

导航

  1. React 基础知识
  2. React 生命周期 与 hooks
  3. React 状态管理:redux
  4. React 框架伙伴:实用脚手架

React 基础知识

React不是mvvm[Vue是],它是单向数据绑定。

MVVM

  • View:界面
  • Model:数据模型
  • ViewModel:作为桥梁负责沟通 View 和 Model

a. VirtualDOM是什么?

用于和真实DOM同步,而在JS内存中维护的一个对象,具有和DOM类似的树状结构,并和DOM可以建立一一对应的关系。实现了多次更新数据层的数据,最后异步处理只进行一次页面重绘。

JSX: 将原始 HTML 模板嵌入到 JS 代码中。必须使用Babelwebpack等工具将其转换为传统的JS

ReactElement将传入的几个属性进行组合,并返回。

  • type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class
  • key:组件的唯一标识,用于Diff算法
  • ref:用于访问原生dom节点
  • props:传入组件的props
  • owner:当前正在构建的Component所属的Component

ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

执行过程

  1. React组件配合state 创建一个虚拟DOM树
  2. 根据虚拟DOM树,生成一个真正的DOM树,再渲染到页面中
  3. state或者props变化时,根据新的数据生成一个新的虚拟DOM
  4. 将新旧虚拟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 事件机制

  1. React 组件绑定事件不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。
  2. JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。

React 生命周期 与 hooks

react.jpg

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])
  1. 无依赖项,每次 render 都会调用
  2. [],页面加载时调用一次
  3. [deps],发生改变时执行,如监听登录状态

setState异步问题

做异步设计是为了性能优化、减少渲染次数

  • 异步:比如在 React 生命周期事件和合成事件中,都会合并延迟更新

  • 同步:在 React 无法控制的地方,比如原生事件的addEventListener 、setTimeoutsetInterval

React 状态管理

Redux全局状态管理⼯具。解决 props 层级过深的问题。

  1. 可以用来处理副作用 side effect,例如异步操作
  2. 优势在于:可以串联和组合 connect,一个项目中使用多个中间件
  3. Redux 中间件用来处理状态更新,也就是在状态更新的过程中,执行一系列相应操作
  • Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store
  • StateStore对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种数据集合就叫做State
  • ActionState的变化,必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
  • Action CreatorView要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator
  • ReducerStore收到Action以后,必须给出一个新的State,这样View才会发生变化。这种计算过程就叫做Reducer。它是一个函数,接受Action和当前State作为参数,返回一个新的State
  • dispatch:是View发出Action的唯一方法。
  1. 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
  2. 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的ActionReducer会返回新的State
  3. State一旦有变化,Store就会调用监听函数,来更新View

reducer.jpg

使用

  1. 创建reducer
    • 可以使用单独的一个reducer,也可以将多个reducer合并为一个reducer,即:combineReducers()
    • action发出命令后将state放入reucer加工函数中,返回新的state,对state进行加工处理
  2. 创建action
    • 需要发出多少动作就有多少指令
    • action是一个对象,使用type定义action类型
  3. 创建的store
    • store 可以理解为有多个加工机器的总工厂
    • 提供subscribedispatchgetState这些方法

React Redux 将组件区分为 容器组件UI 组件

  • Provider 让所有组件都能够访问到Redux中的数据
<Provider store = {store}>
    <App />
<Provider>
  • connectStore 作为 props 绑定到目标组件,mapStateToProps订阅Store更新[把Redux中的数据映射到组件的props中去],mapDispatchToProps改变Store[把各种dispatch也变成props]
    • 获取state: connect通过context获取Provider中的store,通过store.getState()获取整个store tree上所有state
    • 包装原组件: 将stateaction通过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()查看consolelogger中间件的日志信息
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 框架伙伴

好用,常用

参考资料

  1. juejin.cn/post/694154…
  2. github.com/xiaomuzhu/f…
  3. blog.csdn.net/weixin_4316…