首页 react基础知识
文章
取消

react基础知识

生命周期

在 React 中,组件的生命周期函数可以帮助我们管理组件在不同阶段的行为。React 16.3 之前的版本和 React 16.3 之后有所不同。以下是 React 组件生命周期函数的详细列表:

旧的生命周期(React 16.3 之前)

  1. componentWillMount()
    • 在组件即将被挂载到 DOM 之前立刻调用。
  2. componentDidMount()
    • 组件已经被挂载到 DOM 后立刻调用。
  3. componentWillReceiveProps(nextProps)
    • 在组件接收到新的 props 之前立刻调用。此方法在初始化渲染时不会被调用。
  4. shouldComponentUpdate(nextProps, nextState)
    • 在组件接收到新的 props 或 state 时,判断组件是否需要重新渲染。默认返回 true,除非你有特定的优化需要。
  5. componentWillUpdate(nextProps, nextState)
    • 在组件开始重新渲染之前立刻调用。
  6. componentDidUpdate(prevProps, prevState)
    • 组件重新渲染并将更改应用到 DOM 后立刻调用。
  7. componentWillUnmount()
    • 在组件被卸载和销毁之前立刻调用。通常在此方法中执行一些清理工作。
  8. componentDidCatch(error, info)
    • 在子组件抛出错误后被调用。仅在开发模式下以及在生产模式下使用 React 16 时才会被触发。

新的生命周期(React 16.3 之后)

从 React 16.3 开始,旧的生命周期方法已被标记为不推荐使用,并被官方废弃。而新的生命周期主要围绕三个主要阶段:

  1. 挂载(Mounting)
    • constructor()
    • static getDerivedStateFromProps(props, state)
    • render()
    • componentDidMount()
  2. 更新(Updating)
    • static getDerivedStateFromProps(props, state)
    • shouldComponentUpdate(nextProps, nextState)
    • render()
    • getSnapshotBeforeUpdate(prevProps, prevState)
    • componentDidUpdate(prevProps, prevState, snapshot)
  3. 卸载(Unmounting)
    • componentWillUnmount()
  4. 错误处理(Error Handling)
    • static getDerivedStateFromError(error)
    • componentDidCatch(error, info)

这是 React 组件生命周期函数的概览。随着 React 的版本迭代,某些生命周期函数可能会被更改或添加,因此建议查阅官方文档以获取最新信息。

setState 同步还是异步 (react <= 17 版本)

  • 同步表现有:自定义的 DOM 事件, setState 是同步的;异步setTimeout中 setState同步
  • 直接使用 setState 是异步表现

setState 所谓同步还是异步原理

  • setState 无所谓异步还是同步
  • 看是否命中 batchUpdate 机制
  • 判断 isBatchingUpdates

setState 本质是同步

  • setState 是同步,只不过让 React 做成了异步的样子
  • 因力要考虑性能,多次 state 修改,只进行一次 DOM 渲染

哪些能命中 batchUpdate 机制

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

哪些不能命中 batchUpdate 机制

  • setTimeout setInterval等(和它调用的函数)
  • 自定义的 DOM 事件(和它调用的函数)
  • React “管不到”的入口

Alt text

总结:setState 同步与异步的体现主要在于是否命中 batchUpdate机制,在 React入口注册的方法都将会命中 batchUpdate 机制。非 React 入口的函数或者自定义事件,都不会命中 batchUpdate机制. 结论:如果 setState 是在batchUpdate机制中那么就是异步执行,否则就是同步执行

setState(React 18)

  • React 组件事件:异步更新 + 合并 state
  • DOM 事件,setTimeout:异步更新 + 合并 state(react 18版版本后修改的)
  • Automatic Batching 自动批处理

React <=17 React18 总结:

  • React <= 17:只有 React 组件事件才批处理
  • React18:所有事件都自动批处理 Automatic Batching
  • React18:操作一致,更加简单,降低了用户的心智负担

事件event

evnet打印出的并非是对应事件的DOM

  • eventSyntheticEvent,模拟出来的 DOM 事件所有能力
  • event.nativeEvent 是原生事件对象
  • 所有的事件都被挂载到 document
    • react16事件是绑定到 document
    • react17事件绑定到 root组件(有利于多个React版本并存,例如微前端)
  • 和 dom 事件不一样,与vue事件也不一样(vue是直接挂载到对应事件的dom上)

SyntheticEvent为何要用‘合成事件机制’

  • 更好的兼容性和跨平台
  • 转载到 root/document 上,减少内存消耗,避免频繁解绑
  • 方便事件的统一管理(如事务机制)

Portals

使用场景:

  • overflow:hidden(BFC)
  • 父组件 z-index 值太小
  • fixed 需要放到 body 第一层

使用代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {

        }
    }

    render() {
        return ReactDOM.createPortal(
            <div>{this.props.children}</div>,
            document.body   // 要渲染的节点
        )
    }
}

state 不可变值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 不可变值(函数式编程,纯函数)- 数组
const list5copy = this.state.list5.slice()
list5Copy.splice2,0,'a'// 中间插入/删除
this.setState({
    list1: this.state.list1.concat(100), // 追加
    list2: [...this.state.List2, 1001], // 追加
    list3: this.state.list3.slice(O, 3), // 截取
    list4: this.state.list4.filter(item => item > 100) // 筛选
    list5: list5Copy
})
// 注意:直接对 this.state.list 数据进行 push pop splice 等,这样违反不可变值的

// 不可变值 - 对象
this.setState({
    obj1: object.assign({}, this.state.obj1, {a: 100}),
    obj2: {...this.state.obj2, a: 100}
})
// 注意:不能直接 this.state.obj 进行属性设置,这样是违反不可变值

异步组件

  • import
  • React.lazy
  • React.Suspense

使用代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react'
const ContextDemo = React.lazy(() => import('./ContextDemo'))
class App extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <div>
                <p>引入一个动态组件</p>
                <React.Suspense fallback={<div>loading</div>}> 
                    <ContextDemo></ContextDemo>
                </React.Suspense>
            </div>
            // 强制刷新,可看到 loding,异步加载组件加载过程会先有 loading 展示
        )
    }
}

高阶组件 HOC

使用示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
// 高阶组件不是一种功能,而是一种模式
const HOCFactory = (Component) => {
    class HOC extends React.Component {
        // 在此定义多个组件的公共逻辑
        render() {
            return <Component {...this.props}/>
        }
    }
    return HOC
}
const EnhancedComponent1 = HOCFactory (WrappedComponent1)
const EnhancedComponent2 = HOCFactory (WrappedComponent2)

Render Props

  • 核心思想:通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
clss Factoory extends React.Component {
    constructor() {
        this.state = {
            /**多个组件公共逻辑的数据 */
        }
    }

    render() {
        return <div>{this.prop.render(this.state)}</div>
    }
}

const App = () => {
    /** render 是一个函数组件 */
    <Factory render={
        (props) => <p>{props.a} {props.b}</p>
    }/>
}

HOC vs Render Props

  • HOC:模式简单,但会增加组件层级
  • Render Props:代码简洁,学习成本高

immutable.js

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

使用示例:

1
2
3
4
const map1 = Immutable.Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set ('b', 50) 
map1.get ('b') // 2
map2.get ('b') // 50

为什么需要SCU(shouldComponentUpdate)

  • React 默认:父组件有更新,子组件无条件也更新
  • 性能优化对于 React 很重要
  • SCU 一定要每次都用吗? - 需要时才优化

总结:

  • 必须配合“不可变值”一起使用
  • 可先不用 SCU ,有性能问题时再考虑使用
  • SCU 默认返回 true,React 默认重新渲染所有的子组件

PureComponent 和 React.memo

  • PureComponent, SCU 中实现了浅比较
  • memo, 函数组件中的 PureComponent
  • 浅比较已适用大部分情况(尽量不要做深比较)

PureComponent 使用示例:

1
2
3
4
5
6
7
8
9
10
 class App extends React.PureComponent {
    constructor(props) {
        super(props)
    }

    // 组件使用 `PureComponent` 等同于组件自动注入 `shouldComponentUpdate`钩子函数进行的浅比较
    // shouldComponentUpdate(nextProps, nextState) {
    //     /**浅比较 */
    // }
 }

React.memo 使用示例

1
2
3
4
5
6
7
8
9
10
function MyComponent(props) {
    /** 使用props渲染 */
}
function areEqual(prevProps, nextProps) {
    /**
     * 如果把 nextProps 传入 render 方法的返回结果与将 prevProps 传入 render 方法的返回结果一致则返回 true,
     * 否则返回 false
     */
}
export default React.memo(MyComponent, areEqual);

redux

  • 单项数据流(流程)
    • 1、dispathc(action)
    • 2、 reducer -> newState
    • 3、 subscribe 触发通知

redux 在组件中如何使用,connect与消费组件进行连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 将 Redux state 映射到组件的 props
const mapStateToProps = (state: any) => {
    console.log('结构', state);
    return {
        loginReducer: {
            counter: state.loginReducer.counter
        }
        
    }
}

  
// 将 Redux actions 映射到组件的 props
const mapDispatchToProps = (dispatch: any) => ({
    increment: () => dispatch({ type: 'counter/incremented' }),
    decrement: () => dispatch({ type: 'counter/decremented' }),
});

/**
 * 使用 connect 连接组件与 Redux store
 * connect 是高阶组件,将 state dispatch 注入组件 props 中
 * */ 
export default connect(mapStateToProps, mapDispatchToProps)(ClassRedux);

异步 action

使用异步 action 时要先在 createStore 时引入中间件,如下代码:

1
2
3
4
5
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '…/reducers/index';
// 创建 store时,作为中间传引入 redux-thunk
const store = createStore(rootReducer, applyMiddleware(thunk));

下面的是同步 action异步 action 的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 同步 action
export const addTodo = text => {
    // 返回 action 对象
    return {
        type: 'ADD_TODO',
        id: nextTodoId++,
        teext
    }
}

// 异步 action
export const addTodoAsync = text => {
    // 返回函数,其中有 dispatch 参数
    return (dispatch) => {
        // ajax 异步获取数据
        fetch(url).then(res => {
            // 执行异步 action
            dispatch(addTodo(res.text))
        })
    }
}

异步 action 常用的中间件

  • redux-thunk
  • redux-promise
  • redux-sage

Hooks

  • Hooks 调用顺序必须保持一致
  • 如果 Hooks 出现在循环、判断里,则无法保证顺序一致
  • Hooks 严重依赖于调用顺序

    useRef

  • useRef 可以做dom元素操作

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useRef, useEffect } from 'react'

function App() {
    const btnRef = useRef(null) // null 为初始值

    useEffect(() => {
        // 打印 button 的 dom 节点
        console.log(btnRef.current)
    }, [])

    return (
        <div>
            <button ref={btnRef}>click</button>
        </div>
    )
}

export default App

useContext

  • useContext 在函数组件中,接收 Context 传入的数据值

代码示例:

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
import React, { useContext } from 'react'

// 主题色
const themes = {
    light: {
        background: '#eee'
    }
}

// 创建 Context
const ThemeContext = React.createContext(themes.light)

function ThemeButton() {
    const theme = useContext(ThemeContext)
    return (
        <button style={{background: theme.background}}>
            这是一个可以改变颜色的按钮
        </button>
    )
}

function Toolbar() {
    return (
        <>
            <ThemeButton></ThemeButton> 
        </>
    )
}

function App() {
    return (
        <ThemeContext.provider value={themes.light}>
            <Toolbar></Toolbar>
        </ThemeContext.provider>
    )
}

export default App

useReducer

  • useReduceruseState 的代替方案,用于 state 复杂变化
  • useReducer 是单个组件状态管理,组件通讯还需要 props (无法跨组件管理状态,与 redux 不同)
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
import React, { useReducer } from 'react'

const initialState = { count: 0 }
const reducer = (state, action) => {
    switch(action.type) {
        case 'increment':
            return { count: state.count + 1 }
        case 'decrement':
            return { count: state.count - 1 }
    }
}

function App() {
    // useReducer 方法接受两个参数,第一参数:reducer方法,第二个参数:初始值 initialState
    const [state, dispatch] = useReducer(reducer, initialState)

    return (
        <>
            count: {state.count}
            <button onclick={() => dispatch({type:'increment'})}>increment</button>
            <button onclick={() => dispatch({type:'decrement'})}>decrement</button>
        </>
    )
}

export default App

useMemo

  • useMemo 数据缓存,如果依赖项没有变化返回缓存数据(属于 Hooks 性能优化)

使用代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, {useMemo, memo, useState} from "react";

function App() {
    const [name, setName] = useState('饼干')

    // useMemo 缓存数据, 接受两个参数 一个是回调方法,第二个参数是一个数组 依赖项
    const userInfo = useMemo(() => {
        return {name: '饼干', age: 18}
    }, [name]) // 依赖项 name 一旦发生变化,将返回新的值

    return (
        <>
            <div>名字{userInfo.name} 年龄: {userInfo.age}</div>
        </>
    )
}

useCallback

  • useCallback 缓存函数

    useMemo 缓存数据, useCallback 缓存函数,两者都是 React Hooks 的常见优化策略, 当父组件传递给子组件 props(state 和方法)时,父组件可以使用 useCallback 方法缓存 和 useMemo 数据缓存,避免子组件重新渲染减少方法重新创建。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, {useMemo, memo, useState, useCallback} from "react";
function App() {
    const [name, setName] = useState('饼干')

    // useMemo 缓存数据, 接受两个参数 一个是回调方法,第二个参数是一个数组 依赖项
    const userInfo = useMemo(() => {
        return {name: '饼干', age: 18}
    }, [name]) // 依赖项 name 一旦发生变化,将返回新的值

    // useCallback 缓存函数,连个个参数,第一个回调函数,第二个参数依懒项。 针对依赖项如何没有变化避免重新创建函数,提高性能
    const onChange = useCallback(e => {
        console.log(e.target.value)
    }, [])
    
    return (
        <>
            <Children onChange={onChange} userInfo={userInfo}></Children>
        </>
    )
}

useEffect 模拟生命周期

useEffect 让纯函数有了副作用, 函数组件在父组件加载和数据更新时都会直接重新渲染子组件,同时 useEffect 也会调用 useEEffect 默认相当于生命周期 componentDidMount componentDidUpdate这两个生命周期。

接下来使用 useEffect 模拟componentDidMount componentDidUpdate componentWillUnMount 这三个生命周期

useEffect 模拟 componentDidMount

  • 模拟 componentDidMount - useEffect 依赖 [] 示例代码:
    1
    2
    3
    4
    
     // 模拟 class 组件的 DidMount
    useEffect(() => {
     console.log('加载完成')
    }, [])  // 第二个参数是 [](不依赖于任何 state)
    

useEffect 模拟 componentDidUpdate

  • 模拟 componentDidUpdate - useEffect 无依赖,或者依赖[a, b]
    1
    2
    3
    4
    
     // 模拟 class 组件的 DidUpdate
    useEffect(() => {
      console.log('数据更新')
    }, [a, b])  // 第二个参数是依赖的 state
    

useEffect 模拟 componentWillUnMount

  • 模拟 componentWillUnMount - useEffect 中返回一个函数
1
2
3
4
5
6
7
8
// 模拟 class 组件的 WillUnMount
useEffect(() => {
    // 返回一个函数,函数体内处理组件销毁时执行的逻辑
    return () => {
        console.log('组件销毁')
        /**组件销毁时要执行的代码逻辑 */
    }
}, [])  // 第二个参数是 [](不依赖于任何 state)

useEffect 模拟 componentWillUnMount 不完全等于 componentWillUnMount

  • useEffect 在重复执行渲染时,返回函数执行销毁逻辑,会在下一次 useEffect 执行之前被执行,所以它模拟销毁状态时,并不完全等同于 componentWillUnMount 执行机制。
    1
    2
    3
    4
    5
    6
    7
    8
    
    useEffect(() => {
      // 【特别注意】 此处并不完全等同于 WillUnMount
      // 准确的说:返回的函数,会在下一次 effect 执行之前被执行
      return () => {
          console.log('组件销毁')
          /**组件销毁时要执行的代码逻辑 */
      }
    })
    
转摘分享请注明出处

javascript基本类型

vue模板渲染及diff算法