React前端面试题(持续更新...)

前端面试题持续更新…

1.说说对 React 的理解?有哪些特性?

raect是什么
React是一个简单的 javascript UI库,用于构建高效、快速的用户界面。
它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。
它使用虚拟 DOM来有效地操作 DOM
它遵循从高阶组件到低阶组件的单向数据流。
特性
React 特性有很多,如:
  1. JSX 语法
  2. 单向数据绑定
  3. 虚拟 DOM
  4. 声明式编程
  5. Component
优势
高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快

2.说说Real diff算法是怎么运作的?

diff算法

在某一时间节点调用 Reactrender() 方法,会创建一棵由 React 元素组成的树。在下一次 stateprops 更新时,相同的 render() 方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来判断如何有效率的更新 UI 以保证当前 UI 与最新的树保持同步。

React diff策略

  1. Web UIDOM 节点跨层级的移动操作特别少,可以忽略不计。【永远只比较同层节点,不会跨层级比较节点。
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  3. 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分

基于以上三个前提策略,React 分别对 tree diffcomponent diff** 以及 element diff 进行算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能。

tree层:

tree层对DOM节点的跨层级移动的操作忽略不计,只对相同层级的DOM节点进行比较(即同一个父节点下的所有子节点),一旦发现节点不存在,直接删除掉该节点以及之下的所有子节点。

component层:
component层是组件间的比较,有三种策略:

  • 遇到同一类型的组件遵循 tree diff,进行层级对比
  • 遇到不同类型的组件,直接将这个不同的组件判断为脏组件,并替换该组件和之下所有的子节点
  • 在同一类型的两个组件中,当知道这个组件的虚拟dom没有任何变化,就可以手动使用shouldComponentUpdate()来判断组件是否需要进行diff,进一步的提升了diff效率和性能

注意:

避免使用结构相同但是类型不同的组件,由于类型不同的原因,diff会直接销毁该组件并重建,会造成的性能浪费;
对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化

element层:
element层对同一层级的元素节点进行比较,有三种情况:

  1. 面对全新的节点时,执行插入操作
  2. 面对多余的节点时,执行删除操作
  3. 面对换位的节点时,执行移动操作

react中key值的作用

key值就是每个元素节点对应的唯一标识,要进行数据的更新,需要进行新旧虚拟dom的对比,就需要对比子元素的key值是否有匹配项,如果有则会进行数据的更新;如果没有就需要进行删除和重新创建

3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?

生命周期: React整个组件生命周期包括从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程

一共有三个阶段,分别为

  • 挂载阶段(Mounting):已插入真实的Dom阶段

  • 更新阶段(Updating):正在被重新渲染的阶段

  • 卸载阶段(Unmounting):已移出真是dom阶段

旧版生命周期
在这里插入图片描述
新版生命周期
在这里插入图片描述

挂载阶段:

constructor(): 在 React 组件挂载之前,会调用它的构造函数。

componentWillMount(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。

componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用

更新运行阶段:

componentWillReceiveProps(): 在接受父组件改变后的props需要重新渲染组件时用到的比较多,外部组件传递频繁的时候会导致效率比较低

shouldComponentUpdate():用于控制组件重新渲染的生命周期,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新

render(): render() 方法是 class 组件中唯一必须实现的方法。

componentWillUpdate(): shouldComponentUpdate返回true以后,组件进入重新渲染完成之前进入这个函数。

componentDidUpdate(): 每次state改变并重新渲染页面后都会进入这个生命周期

卸载或销毁阶段:

componentWillUnmount (): 在此处完成组件的卸载和数据的销毁。

4.说说你对react hook的理解?

React 中通常使用 类定义 或者 函数定义 创建组件:

在类定义中,我们可以使用到许多 React 特性,例如 state、 各种组件生命周期钩子等,但是在函数定义中,我们却无能为力,因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。

优势

  • 代码逻辑聚合,逻辑复用
  • HOC嵌套地狱
  • 代替class

好处:

  1. 跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
  2. 类定义更为复杂
    ---- 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
    ---- 时刻需要关注this的指向问题;
    ---- 代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
  3. 状态与UI隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离

注意

  • 避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
  • 只有 函数定义组件 和 hooks 可以调用 hooks,避免在 类组件 或者 普通函数 中调用;
  • 不能在useEffect中使用useState,React 会报错提示;
  • 类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存;

常用的 hooks

  • useState()
  • useEffect()
  • useCallback()
  • useMemo()
  • useRef()
  • useContext()
  • useReducer()

Hooks的用法

useState():状态钩子
纯函数组件没有状态,useState()用于设置和使用组件的状态属性。

const [state, setState] = useState(initialValue);
// state:初始的状态属性,指向状态当前值,类似this.state
// setState:修改状态属性值的函数,用来更新状态,类似setState
// initialValue:状态的初始值,该值会赋给state

注意:setState的命名为:set+State(初始状态名),并且采用小驼峰命名法。例如[count, setCount][name, setName]

示例:使用Hooks重写计数器

const Count = () => {
    const [count, setCount] = useState(0); // 将0设置为count的初始值
    const addCount = () => {
        let newCount = count;
        setCount(newCount += 1);
    }
    return (
        <div>
            <p>{count}</p>
            <button onClick={addCount}>1</button>
        </div>
    )
}

用函数组件实现了一个功能完全一样的计数器,代码看起来更加的轻便简洁,没有了继承,没有了渲染逻辑,没有了生命周期等。这就是hooks存在的意义。

useEffect():副作用钩子
useEffect()是副作用的钩子,可以实现特定的功能,如异步请求。语法如下:

useEffect(() => {
    // 回调函数,其中是要进行的异步操作代码
}, [array])
// [array]:useEffect执行的依赖,当该数组的值发生改变时,回调函数中的代码就会被指向
// 如果[array]省略,则表示不依赖,在每次渲染时回调函数都会执行
// 如果[array]是空数组,即useEffect第二项为[],表示只执行一次

示例:通过useEffect()模拟异步加载数据。

const AsyncPage = () => {
    // 首先设置loading状态为true
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        // 2秒后将loading状态设置为false
        setTimeout(() => {
            setLoading(false);
        }, 2000);
    })
    return (
        // 判断loading是否为true,是就显示loading,不是就显示异步请求完成
        loading ? <p>loading...</p> : <p>异步请求完成</p>
    )
}

示例:useEffect()依赖第二项数组变化

const AsyncPage = ({name}) => {
    const [loading, setLoading] = useState(true); // 设置loading状态为true
    const [person, setPerson] = useState({}); // 设置person状态为空对象
    
    useEffect(() => {
        // 首先设置loading为true,2秒后改为false,name改成传过来的参数
        setLoading(true);
        setTimeout(() => {
            setLoading(false);
            setPerson({name});
        }, 2000);
    }, [name]); // 表示当name修改才会执行回调函数
    return (
        <>
            {loading ? <p>Loading...</p> : <p>{person.name}</p>}
        </>
    )
}

const PersonPage = () => {
    // 设置初始state为空字符串
    const [state, setState] = useState("");
    const changeName = (name) => { // 修改name的函数
        setState(name);
    }
    return (
        <>
            {/*首先将state传给name*/}
            <AsyncPage name={state}/>
            <button onClick={() => { // 点击按钮后将张三传给name
                changeName("张三")
            }}>张三
            </button>
            <button onClick={() => {
                changeName("李四")
            }}>李四
            </button>
        </>
    )
}

useEffectuseLayoutEffect的区别
useEffect()useLayoutEffect()主要的区别是调用时机不同。

useLayoutEffect()componentDidMount()componentDidUpate()一致,再react完成DOM更新后马上同步调用代码,它会阻塞页面的渲染,而useEffect()则会在页面渲染完后才会异步调用。

在实际使用中如果项避免页面抖动,可以把需要操作DOM的代码放在useLayoutEffect()中,在该函数中做DOM操作,这些DOM修改会和react做出的更改一起被一次性渲染到屏幕上,只有一次回流重绘的代价。

useCallback(): 记忆函数
useCallback()为记忆函数,它可以防止因为组件重新渲染,导致方法被重新创建,起到缓存作用。语法如下:

useCallback(() => {
    // 回调函数,当array改变后,该函数才会重新声明
}, [array])
// 如果[array]为空数组,那么就是第一次创建后就被缓存,如果后期array改变了,拿到的还是老的array
// 如果不传入第二个参数,每次都会重新声明一次,拿到的就是最新的array

比如说下面一段代码中,我们可以看到有很多的函数,当我们在return中修改一个状态,就会导致整个页面重新渲染,那么这些函数(handleChange1、handleChange2…)也会被重新创建,这样会造成性能的浪费,因此可以使用useCallback将这些函数缓存下来,这样下一次页面重新渲染的时候,某些函数就不会重新被创建了。

        const UseCallback = function () {
            const handleChange1 = () => {
                // 具体代码
            }
            const handleChange2 = () => {
                // 具体代码
            }
            const handleChange3 = () => {
                // 具体代码
            }
            const handleChange4 = () => {
                // 具体代码
            }
            return (
                <div>
                    {/*具体代码*/}
                </div>
            )
        }

使用useCallback()时,只需要将其写在整个函数外部即可,上面代码使用useCallback()后效果如下,每当依赖项改变时,该函数才会被重新创建,如果依赖项不变,则不会重新创建。

        const UseCallback = function () {
            const handleChange1 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            const handleChange2 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            const handleChange3 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            const handleChange4 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            return (
                <div>
                    {/*具体代码*/}
                </div>
            )
        }

**useMemo():**记忆组件
useCallback()的功能可以由useMemo()所替代,useMemo()也可以返回一个记忆函数,语法如下:

useMemo(() => fn, [])
// useCallback(fn, []) = useMemo(() => fn, [])

useCallback()useMemo()的区别:

useCallback()不会执行第一个参数函数,而是将其返回,useMemo()会执行第一个函数并且将函数执行结果返回给你。useCallback()常用记忆时间按函数,生成记忆后的时间函数传递给子组件使用,useMemo()更适合经过函数计算得到一个确定的只,比如记忆组件。

**useRef():**保存引用值
useRef()等价于类组件中的React.createRef(),语法如下:

const loginRef = useRef();

使用useRef()创建了一个值后,就可以将其绑定到DOM节点上,给DOM节点增加一个ref属性,将loginRef传入,则可以通过其current属性获取到DOM节点的值,语法如下:

<input ref={loginRef}/>

除此之外,我们都知道useState()可以保存一个状态,那么另一个方法就是使用useRef(),为useRef()传入一个初始值,它可以帮助我们记住这个状态。

useContext():共享状态钩子
useContext()可以共享状态,作用是进行状态的分发(React16.x以后的版本支持),避免了使用Props进行数据的传递。语法如下:

// 第一步:创建全局的Context
const AppContext = React.createContext([初始化参数])

// 第二步:通过全局的Context进行状态值的共享
<AppContext.Provider value={{ 属性名:}}>
    <组件1 />
    <组件2 />
</AppContext>

示例:A组件和B组件共享一个状态

const Count = () => {
    const AppContext = React.createContext({});
    const A = () => {
        const {name} = useContext(AppContext);
        return (
            <div>
                我是A组件,我的名字是:{name}
            </div>
        )
    }
    const B = () => {
        const {name} = useContext(AppContext);
        return (
            <div>
                我是B组件,我的名字是:{name}
            </div>
        )
    }
    return (
        <AppContext.Provider value={{name: "橘猫吃不胖"}}>
            <A/>
            <B/>
        </AppContext.Provider>
    )
}

useReducer():Action钩子
在使用React的过程中,如遇到状态管理,一般会用到Redux,而React本身是不提供状态管理的。而useReducer()提供了状态管理。

useReducer()useState()的替代方案。首先,关于redux我们都知道,其原理是通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。而Reducer的形式是(state, action) => newstate,返回当前的 state 以及与其配套的 dispatch 方法。。Hooks的useReducer()是这样的:

const [state, dispatch] = useReducer(reducer, initialState)

它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送actiondispatch函数。

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递 dispatch 而不是回调函数。

例如:使用useReducer()实现一个计数器

const Count = () => {
    const reducer = (state, action) => {
        if (action.type == "add") {
            return {
                ...state,
                count: state.count + 1
            }
        } else {
            return state
        }
    }
    const addCount = () => {
        dispatch({
            type: "add"
        })
    }
    const [state, dispatch] = useReducer(reducer, {count: 0});
    return (
        <>
            <p>{state.count}</p>
            <button onClick={addCount}>1</button>
        </>
    )
}

通过代码可以看到,使用useReducer()代替了Redux的功能,但useReducer无法提供中间件等功能,假如有这些需求,还是需要用到redux

5.说说你对受控组件和非受控组件的理解?应用场景?

受控组件:即通过setState的形式控制输入的值及更新,

非受控组件:即通过dom的形式更新值,要获取其值可以通过ref的形式去获取。

受控组件
受控组件,简单来讲,就是受我们控制的组件,组件的状态全程响应外部数据

举个简单的例子:

class TestComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = { username: 'lindaidai' };
  }
  render () {
    return <input name="username" value={this.state.username} />
  }
}

这时候当我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是input标签是一个可读的状态

这是因为valuethis.state.username所控制住。当用户输入新的内容时,this.state.username并不会自动更新,这样的话input内的内容也就不会变了

如果想要解除被控制,可以为input标签设置onChange事件,输入的时候触发事件函数,在函数内部实现state的更新,从而导致input框的内容页发现改变

因此,受控组件我们一般需要初始状态和一个状态更新事件函数

非受控组件
非受控组件,简单来讲,就是不受我们控制的组件

一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态

当需要时,可以使用ref 查询 DOM 并查找其当前值,如下:

import React, { Component } from 'react';
 
export class UnControll extends Component {
  constructor (props) {
    super(props);
    this.inputRef = React.createRef();
  }
  handleSubmit = (e) => {
    console.log('我们可以获得input内的值为', this.inputRef.current.value);
    e.preventDefault();
  }
  render () {
    return (
      <form onSubmit={e => this.handleSubmit(e)}>
        <input defaultValue="lindaidai" ref={this.inputRef} />
        <input type="submit" value="提交" />
      </form>
    )
  }
}

应用场景
大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理

如果选择非受控组件的话,控制能力较弱,表单数据就由DOM本身处理,但更加方便快捷,代码量少

针对两者的区别,其应用场景如下图所示:
在这里插入图片描述

6.谈谈你对react事件处理的理解?

React事件机制

React基于浏览器事件机制实现了一套自己的事件机制,包括:事件注册事件合成事件冒泡事件触发等。

React中的事件处理是什么?

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

为什么要使用合成事件?

  • 进行浏览器兼容,实现更好的跨平台
  • 避免垃圾回收
  • 方便事件统一管理

合成事件与原生事件的区别?

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
  • 合成事件必须显式的使用 preventDefault来组织默认行为,原生事件中可通过return false来实现
  • 使用合成事件不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。只需要在该元素初始渲染的时候添加监听器即可。
  • 向合成事件传参须通过箭头函数或 Function.prototype.bind 来实现

合成事件与原生事件的执行顺序

React 所有事件都挂载在 document 对象上;当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件;所以会先执行原生事件,然后处理 React 事件;最后真正执行 document 上挂载的事件。

所有的原生事件都有对应的合成事件吗?

不是。「几乎」所有事件都代理到了 document,说明有例外,比如audiovideo标签的一些媒体事件(如 onplayonpause 等),是 document 所不具有,这些事件只能够在这些标签上进行事件进行代理,但依旧用统一的入口分发函数(dispatchEvent)进行绑定。

React事件机制总结

  • React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
  • React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()无效的原因。
  • React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
  • React 有一套自己的合成事件 SyntheticEvent

7.谈谈你对redux的理解?

redux是什么

React是用于构建用户界面的,帮助我们解决渲染DOM的过程

而在整个应用中会存在很多个组件,每个组件的state是由自身进行管理,包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享

如果让每个组件都存储自身相关的状态,理论上来讲不会影响应用的运行,但在开发及后续维护阶段,我们将花费大量精力去查询状态的变化过程

这种情况下,如果将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的

redux就是一个实现上述集中管理的容器,遵循三大基本原则:

  • 单一数据源
  • state 是只读的
  • 使用纯函数来执行修改

注意的是,redux并不是只应用在react中,还与其他界面库一起使用,如Vue

redux工作原理

redux 要求我们把数据都放在 store 公共存储空间

一个组件改变了 store 里的数据内容,其他组件就能感知到 store 的变化,再来取数据,从而间接的实现了这些数据传递的功能

工作流程图如下所示:
在这里插入图片描述

根据流程图,可以想象,React Components 是借书的用户, Action Creactor 是借书时说的话(借什么书), Store 是图书馆管理员,Reducer 是记录本(借什么书,还什么书,在哪儿,需要查一下), state 是书籍信息

整个流程就是借书的用户需要先存在,然后需要借书,需要一句话来描述借什么书,图书馆管理员听到后需要查一下记录本,了解图书的位置,最后图书馆管理员会把这本书给到这个借书人

转换为代码是,React Components 需要获取一些数据, 然后它就告知 Store 需要获取数据,这就是就是 Action Creactor , Store 接收到之后去 Reducer 查一下, Reducer 会告诉 Store 应该给这个组件什么数据

redux如何使用
创建一个store的公共数据区域

import { createStore } from 'redux' // 引入一个第三方的方法
const store = createStore() // 创建数据的公共存储区域(管理员)

还需要创建一个记录本去辅助管理数据,也就是reduecer,本质就是一个函数,接收两个参数stateaction,返回state

// 设置默认值
const initialState = {
  counter: 0
}

const reducer = (state = initialState, action) => {
}

然后就可以将记录本传递给store,两者建立连接。如下:

const store = createStore(reducer)

如果想要获取store里面的数据,则通过store.getState()来获取当前state

console.log(store.getState());

下面再看看如何更改store里面数据,是通过dispatch来派发action,通常action中都会有type属性,也可以携带其他的数据

store.dispatch({
  type: "INCREMENT"
})

store.dispath({
  type: "DECREMENT"
})

store.dispatch({
  type: "ADD_NUMBER",
  number: 5
})

下面再来看看修改reducer中的处理逻辑:

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {...state, counter: state.counter + 1};
    case "DECREMENT":
      return {...state, counter: state.counter - 1};
    case "ADD_NUMBER":
      return {...state, counter: state.counter + action.number}
    default: 
      return state;
  }
}

注意,reducer是一个纯函数,不需要直接修改state

这样派发action之后,既可以通过store.subscribe监听store的变化,如下:

store.subscribe(() => {
  console.log(store.getState());
})

React项目中,会搭配react-redux进行使用

完整代码如下:

const redux = require('redux');

const initialState = {
  counter: 0
}

// 创建reducer
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {...state, counter: state.counter + 1};
    case "DECREMENT":
      return {...state, counter: state.counter - 1};
    case "ADD_NUMBER":
      return {...state, counter: state.counter + action.number}
    default: 
      return state;
  }
}

// 根据reducer创建store
const store = redux.createStore(reducer);

store.subscribe(() => {
  console.log(store.getState());
})

// 修改store中的state
store.dispatch({
  type: "INCREMENT"
})
// console.log(store.getState());

store.dispatch({
  type: "DECREMENT"
})
// console.log(store.getState());

store.dispatch({
  type: "ADD_NUMBER",
  number: 5
})
// console.log(store.getState());

小结

  • createStore可以帮助创建 store
  • store.dispatch 帮助派发 action , action 会传递给 store
  • store.getState 这个方法可以帮助获取 store 里边所有的数据内容
  • store.subscrible 方法订阅 store 的改变,只要 store 发生改变, store.subscrible 这个函数接收的这个回调函数就会被执行

8.讲讲 Redux 和 Vuex区别?

相同点

  • state 共享数据
  • 流程一致:定义全局state,触发,修改state
  • 原理相似,通过全局注入store。

不同点

  • 从实现原理上来说
    1. Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改
    2. Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的
  • 从表现层来说
    1. vuex定义了state、getter、mutation、action四个对象;redux定义了state、reducer、action
    2. vuexstate统一存放,方便理解;reduxstate依赖所有reducer的初始值
    3. vuexgetter,目的是快捷得到stateredux没有这层,react-redux mapStateToProps参数做了这个工作。
    4. vuexmutation只是单纯赋值(很浅的一层);reduxreducer只是单纯设置新state(很浅的一层)。他俩作用类似,但书写方式不同
    5. vuexaction有较为复杂的异步ajax请求;reduxaction中可简单可复杂,简单就直接发送数据对象({type:xxx, your-data}),复杂需要调用异步ajax(依赖redux-thunk插件)。
    6. vuex触发方式有两种commit同步和dispatch异步;redux同步和异步都使用dispatch

通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;

共同思想

  • 单一的数据源
  • 变化可以预测

本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。

9.说说Connect组件用法及原理?

1. connect用法

作用:连接React组件与 Redux store

connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
// 这个函数允许我们将 store 中的数据作为 props 绑定到组件上
const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}
  • 这个函数的第一个参数就是 Reduxstore,我们从中摘取了 count 属性。你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性
  • 函数的第二个参数 ownProps,是组件自己的 props

state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给组件

mapDispatchToProps(dispatch, ownProps): dispatchProps

connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props绑定到组件上,也会成为 MyCompprops

2. 原理解析

首先connect之所以会成功,是因为Provider组件

  • 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
  • 接收Reduxstore作为props,通过context对象传递给子孙组件上的connect

connect做了些什么

它真正连接 ReduxReact,它包在我们的容器组件的外一层,它接收上面 Provider提供的 store 里面的 statedispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件

3. 源码

connect是一个高阶函数,首先传入mapStateToPropsmapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有如下特点

  • 通过props.store获取祖先Componentstore props包括statePropsdispatchPropsparentProps,合并在一起得到nextState,作为props传给真正的Component
  • componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
  • shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
  • componentWillUnmount时移除注册的事件this.handleChange
// 主要逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  return function wrapWithConnect(WrappedComponent) {
    class Connect extends Component {
      constructor(props, context) {
        // 从祖先Component处获得store
        this.store = props.store || context.store
        this.stateProps = computeStateProps(this.store, props)
        this.dispatchProps = computeDispatchProps(this.store, props)
        this.state = { storeState: null }
        // 对stateProps、dispatchProps、parentProps进行合并
        this.updateState()
      }
      shouldComponentUpdate(nextProps, nextState) {
        // 进行判断,当数据发生改变时,Component重新渲染
        if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
          this.updateState(nextProps)
            return true
          }
        }
        componentDidMount() {
          // 改变Component的state
          this.store.subscribe(() = {
            this.setState({
              storeState: this.store.getState()
            })
          })
        }
        render() {
          // 生成包裹组件Connect
          return (
            <WrappedComponent {...this.nextState} />
          )
        }
      }
      Connect.contextTypes = {
        store: storeShape
      }
      return Connect;
    }
}

10.说说React Jsx转换成真实DOM过程?

什么是JSX?

  • JSXJavaScript XML。一种在React组件内部构建标签的类XML语法。JSXreact.js开发的一套语法糖,也是react.js的使用基础。React在不使用JSX的情况下一样可以工作,然而使用JSX可以提高组件的可读性,因此推荐使用JSX
class MyComponent extends React.Component {
  render() {
    let props = this.props;  
    return (
      <div className="my-component">
      <a href={props.url}>{props.name}</a>
      </div>
    );
  }
}

jsx的优点

  • 允许使用熟悉的语法来定义 HTML 元素树;
  • 提供更加语义化且移动的标签;
  • 程序结构更容易被直观化;
  • 抽象了 React Element 的创建过程;
  • 可以随时掌控 HTML 标签以及生成这些标签的代码;
  • 是原生的 JavaScript

react 中jsx语法糖的本质

Jsx是语法糖,实质是js函数,需要babel来解析,核心函数是React.createElement(tag,{attrbuties},children),参数tag是标签名可以是html标签和组件名,attrbuties参数是标签的属性,children参数是tag的子元素。用来创建一个vnode,最后渲染到页面上。

React Jsx转换成真实DOM是什么
react通过将组件编写的JSX映射到屏幕,以及组件中的状态发生了变化之后 React会将这些「变化」更新到屏幕上

在前面文章了解中,JSX通过babel最终转化成React.createElement这种形式,例如:

<div>
  <img src="avatar.png" className="profile" />
  <Hello />
</div>

会被bebel转化成如下:

React.createElement(
  "div",
  null,
  React.createElement("img", {
    src: "avatar.png",
    className: "profile"
  }),
  React.createElement(Hello, null)
);

在转化过程中,babel在编译时会判断 JSX 中组件的首字母:

  • 当首字母为小写时,其被认定为原生 DOM 标签,createElement 的第一个变量被编译为字符串
  • 当首字母为大写时,其被认定为自定义组件,createElement 的第一个变量被编译为对象

最终都会通过RenderDOM.render(...)方法进行挂载,如下:

ReactDOM.render(<App />,  document.getElementById("root"));

过程
react中,节点大致可以分成四个类别:

  • 原生标签节点
  • 文本节点
  • 函数组件
  • 类组件
    如下所示:
class ClassComponent extends Component {
  static defaultProps = {
    color: "pink"
  };
  render() {
    return (
      <div className="border">
        <h3>ClassComponent</h3>
        <p className={this.props.color}>{this.props.name}</p >
      </div>
    );
  }
}

function FunctionComponent(props) {
  return (
    <div className="border">
      FunctionComponent
      <p>{props.name}</p >
    </div>
  );
}

const jsx = (
  <div className="border">
    <p>xx</p >
    < a href=" ">xxx</ a>
    <FunctionComponent name="函数组件" />
    <ClassComponent name="类组件" color="red" />
  </div>
);

这些类别最终都会被转化成React.createElement这种形式

React.createElement其被调用时会传⼊标签类型type,标签属性props及若干子元素children,作用是生成一个虚拟Dom对象,如下所示:

function createElement(type, config, ...children) {
    if (config) {
        delete config.__self;
        delete config.__source;
    }
    // ! 源码中做了详细处理,⽐如过滤掉key、ref等
    const props = {
        ...config,
        children: children.map(child =>
   typeof child === "object" ? child : createTextNode(child)
  )
    };
    return {
        type,
        props
    };
}
function createTextNode(text) {
    return {
        type: TEXT,
        props: {
            children: [],
            nodeValue: text
        }
    };
}
export default {
    createElement
};

createElement会根据传入的节点信息进行一个判断:

  • 如果是原生标签节点, type 是字符串,如divspan
  • 如果是文本节点, type就没有,这里是 TEXT
  • 如果是函数组件,type 是函数名
  • 如果是类组件,type 是类名

虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,使用方法如下:

ReactDOM.render(element, container[, callback])

当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 Reactdiff算法进行高效的更新

如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行

render大致实现方法如下:

function render(vnode, container) {
    console.log("vnode", vnode); // 虚拟DOM对象
    // vnode _> node
    const node = createNode(vnode, container);
    container.appendChild(node);
}

// 创建真实DOM节点
function createNode(vnode, parentNode) {
    let node = null;
    const {type, props} = vnode;
    if (type === TEXT) {
        node = document.createTextNode("");
    } else if (typeof type === "string") {
        node = document.createElement(type);
    } else if (typeof type === "function") {
        node = type.isReactComponent
            ? updateClassComponent(vnode, parentNode)
        : updateFunctionComponent(vnode, parentNode);
    } else {
        node = document.createDocumentFragment();
    }
    reconcileChildren(props.children, node);
    updateNode(node, props);
    return node;
}

// 遍历下子vnode,然后把子vnode->真实DOM节点,再插入父node中
function reconcileChildren(children, node) {
    for (let i = 0; i < children.length; i++) {
        let child = children[i];
        if (Array.isArray(child)) {
            for (let j = 0; j < child.length; j++) {
                render(child[j], node);
            }
        } else {
            render(child, node);
        }
    }
}
function updateNode(node, nextVal) {
    Object.keys(nextVal)
        .filter(k => k !== "children")
        .forEach(k => {
        if (k.slice(0, 2) === "on") {
            let eventName = k.slice(2).toLocaleLowerCase();
            node.addEventListener(eventName, nextVal[k]);
        } else {
            node[k] = nextVal[k];
        }
    });
}

// 返回真实dom节点
// 执行函数
function updateFunctionComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let vvnode = type(props);
    const node = createNode(vvnode, parentNode);
    return node;
}

// 返回真实dom节点
// 先实例化,再执行render函数
function updateClassComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let cmp = new type(props);
    const vvnode = cmp.render();
    const node = createNode(vvnode, parentNode);
    return node;
}
export default {
    render
};

总结
react源码中,虚拟Dom转化成真实Dom整体流程如下图所示:
在这里插入图片描述
其渲染流程如下所示:

  • 使用React.createElementJSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(...)Babel帮助我们完成了这个转换的过程。
  • createElement函数对keyref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

11.React 组件间怎么进行通信?

通信是什么
我们将组件间通信可以拆分为两个词:

  • 组件
  • 通信

React的组件灵活多样,按照不同的方式可以分成很多类型的组件

而通信指的是发送者通过某种媒体以某种格式来传递信息到收信者以达到某个目的,广义上,任何信息的交通都是通信

组件间通信即指:组件通过某种方式来传递信息以达到某个目的

如何通信
组件传递的方式有很多种,根据传送者接收者可以分为如下:

  • 父组件向子组件传递
  • 子组件向父组件传递
  • 兄弟组件之间的通信
  • 父组件向后代组件传递
  • 非关系组件传递

父组件向子组件传递
由于React的数据流动为单向的,父组件向子组件传递是最常见的方式

父组件在调用子组件的时候,只需要在子组件标签内传递参数,子组件通过props属性就能接收父组件传递过来的参数

function EmailInput(props) {
  return (
    <label>
      Email: <input value={props.email} />
    </label>
  );
}

const element = <EmailInput email="123124132@163.com" />;

子组件向父组件传递

子组件向父组件通信的基本思路是,父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值

父组件对应代码如下:

class Parents extends Component {
  constructor() {
    super();
    this.state = {
      price: 0
    };
  }

  getItemPrice(e) {
    this.setState({
      price: e
    });
  }

  render() {
    return (
      <div>
        <div>price: {this.state.price}</div>
        {/* 向子组件中传入一个函数  */}
        <Child getPrice={this.getItemPrice.bind(this)} />
      </div>
    );
  }
}

子组件对应代码如下:

class Child extends Component {
  clickGoods(e) {
    // 在此函数中传入值
    this.props.getPrice(e);
  }

  render() {
    return (
      <div>
        <button onClick={this.clickGoods.bind(this, 100)}>goods1</button>
        <button onClick={this.clickGoods.bind(this, 1000)}>goods2</button>
      </div>
    );
  }
}

兄弟组件之间的通信

如果是兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
  }
  setCount = () => {
    this.setState({count: this.state.count + 1})
  }
  render() {
    return (
      <div>
        <SiblingA
          count={this.state.count}
        />
        <SiblingB
          onClick={this.setCount}
        />
      </div>
    );
  }
}

父组件向后代组件传递

父组件向后代组件传递数据是一件最普通的事情,就像全局数据一样

使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据

通过使用React.createContext创建一个context

 const PriceContext = React.createContext('price')

context创建成功后,其下存在Provider组件用于创建数据源,Consumer组件用于接收数据,使用实例如下:

Provider组件通过value属性用于给后代组件传递数据:

<PriceContext.Provider value={100}>
</PriceContext.Provider>

如果想要获取Provider传递的数据,可以通过Consumer组件或者或者使用contextType属性接收,对应分别如下:

class MyClass extends React.Component {
  static contextType = PriceContext;
  render() {
    let price = this.context;
    /* 基于这个值进行渲染工作 */
  }
}

Consumer组件:

<PriceContext.Consumer>
    { /*这里是一个函数*/ }
    {
        price => <div>price:{price}</div>
    }
</PriceContext.Consumer>

非关系组件传递

如果组件之间关系类型比较复杂的情况,建议将数据进行一个全局资源管理,从而实现通信,例如redux。关于redux的使用后续再详细介绍

总结
由于React是单向数据流,主要思想是组件不会改变接收的数据,只会监听数据的变化,当数据发生变化时它们会使用接收到的新值,而不是去修改已有的值

因此,可以看到通信过程中,数据的存储位置都是存放在上级位置中

12.说说你对fiber架构的理解?解决了什么问题?

React 的核心流程可以分为两个部分:

  • reconciliation (调度算法,也可称为 render)
    • 更新 stateprops
    • 调用生命周期钩子;
    • 生成 virtual dom
      • 这里应该称为 Fiber Tree 更为符合;
    • 通过新旧 vdom 进行 diff 算法,获取 vdom change
    • 确定是否需要重新渲染
  • commit
    • 如需要,则操作 dom 节点更新

要了解 Fiber,我们首先来看为什么需要它

  • 问题:
    • 随着应用变得越来越庞大,整个更新渲染的过程开始变得吃力,大量的组件渲染会导致主进程长时间被占用,导致一些动画或高频操作出现卡顿和掉帧的情况。而关键点,便是 同步阻塞。在之前的调度算法中,React 需要实例化每个类组件,生成一颗组件树,使用 同步递归 的方式进行遍历渲染,而这个过程最大的问题就是无法 暂停和恢复。
  • 解决方案:
    • 解决同步阻塞的方法,通常有两种: 异步 与 任务分割。而 React Fiber 便是为了实现任务分割而诞生的
  • 简述
    • React V16 将调度算法进行了重构, 将之前的 stack reconciler 重构成新版的 fiber reconciler,变成了具有链表和指针的 单链表树遍历算法。通过指针映射,每个单元都记录着遍历当下的上一步与下一步,从而使遍历变得可以被暂停和重启
    • 这里我理解为是一种 任务分割调度算法,主要是 将原先同步更新渲染的任务分割成一个个独立的 小任务单位,根据不同的优先级,将小任务分散到浏览器的空闲时间执行,充分利用主进程的事件循环机制
  • 核心
    • Fiber 这里可以具象为一个 数据结构
class Fiber {
	constructor(instance) {
		this.instance = instance
		// 指向第一个 child 节点
		this.child = child
		// 指向父节点
		this.return = parent
		// 指向第一个兄弟节点
		this.sibling = previous
	}	
}
  • 链表树遍历算法: 通过 节点保存与映射,便能够随时地进行 停止和重启, 这样便能达到实现任务分割的基本前提
    • 首先通过不断遍历子节点,到树末尾;
    • 开始通过 sibling 遍历兄弟节点;
    • return 返回父节点,继续执行2;
    • 直到 root 节点后,跳出遍历;
  • 任务分割,React 中的渲染更新可以分成两个阶段
    • reconciliation 阶段: vdom 的数据对比,是个适合拆分的阶段,比如对比一部分树后,先暂停执行个动画调用,待完成后再回来继续比对
    • Commit 阶段: 将 change list 更新到 dom 上,并不适合拆分,才能保持数据与 UI 的同步。否则可能由于阻塞 UI 更新,而导致数据更新和 UI 不一致的情况
  • 分散执行: 任务分割后,就可以把小任务单元分散到浏览器的空闲期间去排队执行,而实现的关键是两个新API: requestIdleCallbackrequestAnimationFrame
    • 低优先级的任务交给requestIdleCallback处理,这是个浏览器提供的事件循环空闲期的回调函数,需要 pollyfill,而且拥有 deadline 参数,限制执行事件,以继续切分任务;
    • 高优先级的任务交给requestAnimationFrame处理;
// 类似于这样的方式
requestIdleCallback((deadline) => {
    // 当有空闲时间时,我们执行一个组件渲染;
    // 把任务塞到一个个碎片时间中去;
    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {
        nextComponent = performWork(nextComponent);
    }
});
  • 优先级策略:

文本框输入 > 本次调度结束需完成的任务 > 动画过渡 > 交互反馈 > 数据更新 > 不会显示但以防将来会显示的任务

React的Fiber工作原理,解决了什么问题

React Fiber 是一种基于浏览器的单线程调度算法。

React Fiber 用类似 requestIdleCallback 的机制来做异步 diff。但是之前数据结构不支持这样的实现异步 diff,于是 React 实现了一个类似链表的数据结构,将原来的 递归diff 变成了现在的 遍历diff,这样就能做到异步可更新了

  • Fiber 其实可以算是一种编程思想,在其它语言中也有许多应用(Ruby Fiber)。
  • 核心思想是 任务拆分和协同,主动把执行权交给主线程,使主线程有时间空挡处理其他高优先级任务。
  • 当遇到进程阻塞的问题时,任务分割、异步调用 和 缓存策略 是三个显著的解决思路。

Fiber 是什么
Fiber 的中文翻译叫纤程,与进程、线程同为程序执行过程,Fiber 就是比线程还要纤细的一个过程。纤程意在对渲染过程实现进行更加精细的控制。

react中,主要做了以下的操作:

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行

从架构角度来看,Fiber 是对 React 核心算法(即调和过程)的重写。

从编码角度来看,FiberReact 内部所定义的一种数据结构,它是 Fiber 树结构的节点单位,也就是 React 16 新架构下的"虚拟 DOM"。

一个 fiber 就是一个 JavaScript 对象,Fiber 的数据结构如下:

type Fiber = {
  // 用于标记fiber的WorkTag类型,主要表示当前fiber代表的组件类型如FunctionComponent、ClassComponent等
  tag: WorkTag,
  // ReactElement里面的key
  key: null | string,
  // ReactElement.type,调用`createElement`的第一个参数
  elementType: any,
  // The resolved function/class/ associated with this fiber.
  // 表示当前代表的节点类型
  type: any,
  // 表示当前FiberNode对应的element组件实例
  stateNode: any,

  // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
  return: Fiber | null,
  // 指向自己的第一个子节点
  child: Fiber | null,
  // 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
  sibling: Fiber | null,
  index: number,

  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,

  // 当前处理过程中的组件props对象
  pendingProps: any,
  // 上一次渲染完成之后的props
  memoizedProps: any,

  // 该Fiber对应的组件产生的Update会存放在这个队列里面
  updateQueue: UpdateQueue<any> | null,

  // 上一次渲染的时候的state
  memoizedState: any,

  // 一个列表,存放这个Fiber依赖的context
  firstContextDependency: ContextDependency<mixed> | null,

  mode: TypeOfMode,

  // Effect
  // 用来记录Side Effect
  effectTag: SideEffectTag,

  // 单链表用来快速查找下一个side effect
  nextEffect: Fiber | null,

  // 子树中第一个side effect
  firstEffect: Fiber | null,
  // 子树中最后一个side effect
  lastEffect: Fiber | null,

  // 代表任务在未来的哪个时间点应该被完成,之后版本改名为 lanes
  expirationTime: ExpirationTime,

  // 快速确定子树中是否有不在等待的变化
  childExpirationTime: ExpirationTime,

  // fiber的版本池,即记录fiber更新过程,便于恢复
  alternate: Fiber | null,
}

Fiber 如何解决问题的
Fiber 把一个渲染任务分解为多个渲染任务,而不是一次性完成,把每一个分割得很细的任务视作一个"执行单元",React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去,故任务会被分散到多个帧里面,中间可以返回至主进程控制执行其他任务,最终实现更流畅的用户体验。

即是实现了"增量渲染",实现了可中断与恢复,恢复后也可以复用之前的中间状态,并给不同的任务赋予不同的优先级,其中每个任务更新单元为 React Element 对应的 Fiber 节点。

Fiber 实现原理
实现的方式是requestIdleCallback这一 API,但 React 团队 polyfill 了这个 API,使其对比原生的浏览器兼容性更好且拓展了特性。

window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间 timeout,则有可能为了在超时前执行函数而打乱执行顺序。

requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。

requestIdleCallback的作用是在浏览器一帧的剩余空闲时间内执行优先度相对较低的任务。首先 React 中任务切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间,再继续之前 React 未完成的任务,是一种合作式调度。

简而言之,由浏览器给我们分配执行时间片,我们要按照约定在这个时间内执行完毕,并将控制权还给浏览器。

React 16Reconciler基于 Fiber 节点实现,被称为 Fiber Reconciler

作为静态的数据结构来说,每个 Fiber 节点对应一个 React element,保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。

作为动态的工作单元来说,每个 Fiber 节点保存了本次更新中该组件改变的状态、要执行的工作。

每个 Fiber 节点有个对应的 React element,多个 Fiber 节点是如何连接形成树呢?靠如下三个属性:

复制
// 指向父级Fiber节点
this.return = null
// 指向子Fiber节点
this.child = null
// 指向右边第一个兄弟Fiber节点
this.sibling = null

13.React 中的 setState 同步异步的问题?

什么时候同步什么时候异步

React 中,如果是由 React 引发的事件处理(比如通过 onClick 引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的 setState 调用会同步更新 this.state

所谓的除此之外,指的是绕过 React,通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval 产生的同步任务

原因

ReactsetState 函数实现中,会根据一个变量 isBatchingUpdate 判断是直接更新 this.state 还是放到队列中回头再说,而且 isBatchingUpdate 默认是 false,也就是表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdate,这个函数会把 isBatchingUpdate 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 this.state

注意

setState 的 异步 并不是说内部由异步代码实现,其实本身执行的过程和代码是同步的,只是合成事件和钩子函数的调用在更新之前,导致在合成事件和钩子函数中没法里吗拿到更新后的值,形成了所谓的"异步"

但是可以通过 setState(partialState, callback) 中的 callback 拿到更新后的结果

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值