Glittering's blog Glittering's blog
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)

Glitz Ma

前端开发工程师
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • 技术文档

  • 算法

  • 工作总结

    • 时区校正
    • 上传下载文件方式总结
    • webpack优化实践
    • webpack基础应用知识总结
    • vue常见原理总结
    • vue基础知识总结
    • react高级特性
      • 特性
        • 函数组件
        • 非受控组件
        • portals
        • context
        • 异步组件
        • shouldComponentUpdate
        • pureComponent
        • immutable.js
        • 高阶组件HOC
        • render Props
        • redux过程描述
      • react本质
        • vdom和diff
        • jsx
        • 合成事件
        • setState
        • 组件渲染、更新过程
    • react基础知识总结
    • 微前端总结
    • 今天总结一下用到的css吧
    • 地图标绘--射线法来计算点在多边形内
    • web异常监控和分析
    • 工作中遇到的css问题记录
    • axios 与 promise
    • 前端优化指南
    • 小程序笔记
    • JS随机打乱数组
    • 非常实用的JavaScript一行代码
  • 实用技巧

  • 收藏夹

  • 技术
  • 工作总结
mamingjuan
2021-03-01
目录

react高级特性

# 特性

函数组件、非受控组件、受控组件、portals、context、异步组件、高阶组件HOC、render props、pureComponent、shouldComponentUpdate、immutable.js

# 函数组件

当class组件只接收props,然后通过props返回渲染jsx的组件,它没有实例没有生命周期、没有state不能扩展其它方法

# 非受控组件

通过ref控制dom。不能通过setState更改数据,如文件上传。

优先使用受控组件,符合react设计原则,必须操作dom时使用非受控组件。

# portals

组件会按照默认层次嵌套渲染,子组件正常渲染时,可获取父组件的children节点通过{this.props.children}。

  • 当父组件overflow:hidden
  • 父组件设了BFC,限制子组件展示
  • 父组件z-index太小
  • 需要fixed放到body层时

使用portals可以让子组件逃离父组件

# context

公共信息(语言、主题)这些通过props传递太繁琐,通过redux小题大作


//1.  创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext('light')

//2. 将组件用声明的ThemeContext包起来
class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            theme: 'light'
        }
    }
    render() {
        return <ThemeContext.Provider value={this.state.theme}>
            <Toolbar />
            <hr/>
            <button onClick={this.changeTheme}>change theme</button>
        </ThemeContext.Provider>
    }
    changeTheme = () => {
        this.setState({
            theme: this.state.theme === 'light' ? 'dark' : 'light'
        })
    }
}

//3. 函数式组件
// 底层组件 - 函数是组件
function ThemeLink (props) {
    // const theme = this.context // 会报错。函数式组件没有实例,即没有 this

    // 函数式组件可以使用 Consumer
    return <ThemeContext.Consumer>
        { value => <p>link's theme is {value}</p> }
    </ThemeContext.Consumer>
}

//4. class组件
// 底层组件 - class 组件
class ThemedButton extends React.Component {
    // 指定 contextType 读取当前的 theme context。
    // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
    render() {
        const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
        return <div>
            <p>button's theme is {theme}</p>
        </div>
    }
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 异步组件

import() 、react.lazy、react.suspense

react.suspense加loading

import React from 'react'

const ContextDemo = React.lazy(() => import('./ContextDemo'))

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            <p>引入一个动态组件</p>
            <hr />
            <React.Suspense fallback={<div>Loading...</div>}>
                <ContextDemo/>
            </React.Suspense>
        </div>

        // 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
        // 2. 看 network 的 js 加载
    }
}

export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# shouldComponentUpdate

React默认父组件有更新时子组件无条件更新

  • shouldComponentUpdate(scu)默认返回true。
  • 默认情况下重新渲染所有子组件。
  • 必须配合不可变值一起使用,否则会出现无法更新的问题
  • 可以不处处都用scu,有性能问题时再使用
shouldComponentUpdate(nextProps, nextState){
    if(nextProps.text !== this.props.text){
        return true; // 可以渲染
    } else {
        return false; // 不重复渲染
    }
}
1
2
3
4
5
6
7

# pureComponent

  • PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。
  • 可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,界面得不到更新。
  • 在函数组件中用memo是pureComponent
  • 浅比较适用于大部分情况,尽量不要做深比较

# immutable.js

  • 彻底拥抱不可变值
  • 基于共享数据(不是深拷贝)速度好
  • 有一定的学习迁移成本,按需使用

# 高阶组件HOC

  • 高阶组件不是一种功能,而是一种模式
  • HOC模式简单但会增加组件层级
  • 高阶组件透传所有props,vue中用v-bind传递$props
import React from 'react'

// 高阶组件
const withMouse = (Component) => {
    class withMouseComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = { x: 0, y: 0 }
        }
  
        handleMouseMove = (event) => {
            this.setState({
                x: event.clientX,
                y: event.clientY
            })
        }
  
        render() {
            return (
                <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
                    {/* 1. 透传所有 props 2. 增加 mouse 属性 */}
                    <Component {...this.props} mouse={this.state}/>
                </div>
            )
        }
    }
    return withMouseComponent
}

const App = (props) => {
    const a = props.a
    const { x, y } = props.mouse // 接收 mouse 属性
    return (
        <div style={{ height: '500px' }}>
            <h1>The mouse position is ({x}, {y})</h1>
            <p>{a}</p>
        </div>
    )
}

export default withMouse(App) // 返回高阶函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# render Props

通过一个函数将class组件的state作为props传递给纯函数组件。在render Props中只是赋予一种能力,具体如何渲染还是父组件说了算。

import React from 'react'
import PropTypes from 'prop-types'

class Mouse extends React.Component {
    constructor(props) {
        super(props)
        this.state = { x: 0, y: 0 }
    }
  
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }
  
    render() {
      return (
        <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
            {/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
            {this.props.render(this.state)}
        </div>
      )
    }
}
Mouse.propTypes = {
    render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}

const App = (props) => (
    <div style={{ height: '500px' }}>
        <p>{props.a}</p>
        <Mouse render={
            /* render 是一个函数组件 */
            ({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
        }/>
        
    </div>
)

/**
 * 即,定义了 Mouse 组件,只有获取 x y 的能力。
 * 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
 */

export default App

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# redux过程描述

定义state,然后dispatch action, 被reducer接收,判断action.type返回state处理。然后触发state更新store.subscribe(()=>{}) 触发通知用于更新视图。

// 这样就有了redux的能力
import { createStore} from 'redux';
import todoApp from './reducers';
import App from './component/App';

let store = createStore(todoApp);

export default function(){
    return <Provider store={store}>
        <App/>
    </Provider>;
}

1
2
3
4
5
6
7
8
9
10
11
12
13

let AddTodo = ({ dispatch }) => {
    // dispatch为props.dispatch

    dispatch({type: "INCREMENT"});
    dispatch(addTodo(''))
}


// 相消费redux提供的能力需要通过connet调用,通过connect高阶组件dispatch作为props注入到AddTodo组件中
AddTodo = connect()(AddTodo)

export default AddTodo;
1
2
3
4
5
6
7
8
9
10
11
12
13

redux-thunk可以异步action

import thunk from 'redux-thunk';

const store = createStore(reducerName, applyMiddleWare(thunk));// 多个middleWare传递参数用逗号隔开

1
2
3
4

# react本质

  • 函数式编程
  • vdom和diff
  • jsx
  • 合成事件
  • setState batchUpdate

# vdom和diff

通过React.createElement函数,生成vnode数据结构,然后调用patch更新。

  • 只比较同级,不跨级比较
  • tag不同,则直接删掉重建,不再深入比较
  • tag和key,两者都相同,则认为是相同的节点
  • vue2.x、vue3.x、react三者实现vdom细节都不同,但核心概念和实现思路一致

# jsx

jsx是react语法糖,它其实是会编译成React.createElement(div,null,[...])通过render函数生成vnode然后根据vnode渲染。

# 合成事件

  • 所有事件挂载到document上
  • event不是原生的,是syntheticEvent合成事件对象
  • 它和vue事件不同,和dom原生事件不同(vue是原生的dom事件)

为何要合成事件机制?

  • 更好的兼容性和跨平台
  • 挂载到document,减少内存消耗,避免频繁解绑
  • 方便事件统一管理

# setState

setState是同步还是异步要看不否命中batchUpdate机制,判断isBatchingUpdates。命中则保存到dirtyComponents中。没有命中则遍历dirtyComponents、调用updateComponent,更新pending state or props。

class MyDemo extends React.Component{
    constructor(props){

    }

    render(){

    }

    increase = () => {
        // 开始:处于batchUpdate
        // isBatchingUpdates = true;
        this.setState({
            count: this.state.count + 1,
        })
        // 结束: isBatchingUpdates = false;
    }

    reduce = () => {
        // 开始: 处于batchUpdate
        // isBatchingUpdates = true;
        setTimeoute(() => {
            // 此时: isBatchingUpdates = false;
            this.setState({
                count: this.state.count - 1,
            })
        })
        // 结束: isBatchingUpdates = false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

能命中batchUpdate

  • 生命周期和它调用的函数
  • React中注册的事件和它调用的函数
  • React可以管理的入口

不能命中batchUpdate

  • setTimeout、setInterval和它调用的函数
  • 自定义的dom事件和它调用的函数
  • react管不到的入口

# 组件渲染、更新过程

渲染过程

  • props state
  • render生成vnode
  • patch(elem, vnode)

组件更新过程

  • setState(newState) --> dirtyComponent
  • 遍历dirtyComponent组件去render生成newVnode
  • patch(vnode,newVnode)

上述的patch被拆分为两个阶段

  1. reconciliation阶段 --> 执行diff算法、纯js计算
  2. commit阶段 --> 将diff结果渲染到dom中

js是单线程的,且和dom渲染共用一个线程。当组件足够复杂时,组件更新时计算和渲染压力足够大时(如同时有动画,鼠标拖拽等功能时)会卡顿。

  • 将reconciliation阶段进行任务拆分(commit阶段无法拆分)
  • dom需要渲染时暂停,空闲时恢复。通过window.requestIdleCallback来实现

上面说的就是react fiber
React Fiber把更新过程碎片化,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。

上次更新: 2025/04/07, 01:42:58
vue基础知识总结
react基础知识总结

← vue基础知识总结 react基础知识总结→

Copyright © 2015-2025 Glitz Ma
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式