Skip to content

【面试题】React知识点整理(附答案)  #129

Open
@funnycoderstar

Description

@funnycoderstar

目录

  1. setState原理,什么时候同步,什么时候异步

  2. JSX原理,为什么自定义的React组件必须大写

  3. 虚拟DOM,diff算法

    • 虚拟DOM是什么
    • React diff原理,如何从 O(n^3)变成 O(n)
    • 为什么要使用Key,key有什么好处
  4. 生命周期(16版本之前和之后的)

  5. React事件机制

    • React如何实现自己的事件机制
    • React事件和原生事件有什么区别
    • React 事件中为什么要绑定 this 或者 要用箭头函数, 他们有什么区别
  6. fiber

  7. mixin、HOC、render props、hooks

  8. immber和immutable

  9. 受控组件和非受控组件区别

  10. redux,redux 和 mobx 区别,为什么选择 redux

  11. Vue和React的区别

一.setState原理,什么时候同步,什么时候异步

这里的“异步”不是说异步代码实现,而是说 React会收集变更,然后在统一更新。

在React中,如果是由React引发的事件处理(比如通过 onClick引发的事件处理),调用 setState 不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓"除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval产生的异步调用。

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

二.JSX原理,为什么自定义的React组件必须大写

JSX原理

实际上,JSX仅仅只是 React.createElement(component, props, ...children)函数的语法糖。如下JSX代码

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

会编译为

React.createElement(MyButton, {
  color: "blue",
  shadowSize: 2
}, "Click Me");

如果没有子节点,你还可以使用自闭合的标签形式,如

<div className="sidebar" />

会编译为

React.createElement("div", {
  className: "sidebar"
});

可以直接在 babeljs中尝试。

为什么自定义的React组件必须大写

React.createElement(component, props, ...children)的第一个参数 component的类型是 string/ReactClass type

  • string 类型 React会当做原生的DOM节点进行解析
  • ReactClass type 类型 自定义组件

简而言之,babel在编译过程中会判断 JSX 组件的首字母,如果是小写,则当做原生的DOM标签解析,就编译成字符串。如果是大写,则认为是自定义组件,编译成对象。

三. 虚拟DOM, diff算法

虚拟DOM是什么

虚拟DOM 就是使用一个 原生的JavaScript对象来描述 一个DOM节点。

<div id="wrap">
    <p class="text">好好学习,天天向上</p>
</div>

使用虚拟DOM表示如下:

const element = {
  // 标签名
  tagName: 'div',
  properties: {
    id: 'wrap',
  },
  children: [
    {
      tagName: 'p',
      properties: {
        class: 'text',
        children: ['好好学习,天天向上']
      },
    }
  ]
}

React diff原理,如何从 O(n^3)变成 O(n)

其核心是基于两个简单的假设:

  1. 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构
  2. 同一层级的一组节点,他们可以通过唯一的id进行区分。

基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n^3)降到了O(n)。

React’s diff algorithm

生命周期(16版本之前和之后的)

16版本之前的生命周期

第一次渲染

constructor -> componentWillMount -> render -> componentDidMount

props更新

componentWillReceiveProps(newProps) -> shouldComponentUpdate((nextProps, nextState))(如果返回true) -> componentUillUpdate(nextProps, nextState) -> render -> componentDidUpdate(prevProps, prevState)

state更新

shouldComponentUpdate((nextProps, nextState))(如果返回true) -> componentUillUpdate(nextProps, nextState) -> render -> componentDidUpdate(prevProps, prevState)

组件卸载

componentWillUnmount

V16.3 之前的生命周期3

16版本之后的生命周期

第一次渲染

constructor -> getDerivedStateFromProps(nextProps, prevState) -> render -> componentDidMount

props或state更新

getDerivedStateFromProps(nextProps, prevState) -> shouldComponentUpdate((nextProps, nextState))(如果返回true) -> render -> getSnapshotBeforeUpdate(prevProps, prevState) ->  componentDidUpdate(prevProps, prevState)

组件卸载

componentWillUnmount

五.React事件机制

React事件和原生事件有什么区别

  • React事件使用驼峰命名,而不是纯小写
  • 通过JSX,传递一个函数作为事件处理程序,而不是一个字符串
// 传统的HTML
<button onclick="getCount()">
  点击
</button>

// 在react中
<button onClick={getCount}>
  点击
</button>
  • 在React中,你不能通过返回 false 的方式阻止默认行为,必须显式的使用 preventDefault;

React 事件中为什么要绑定 this 或者 要用箭头函数, 他们有什么区别

function invokeGuardedCallback(name, func, a) {
  try {
    func(a);
  } catch (x) {
    if (caughtError === null) {
      caughtError = x;
    }
  }
}

回调函数是直接调用的,如果不手动绑定 this,获取到的 this为 undefined

六.fiber

render之前的生命周期,即将被废弃
componentWillMount
componentWillUpdate
componentWillReceiveProps

在16.x版本之前,每个生命周期在加载或更新过程中只会调用一次,因为新的fiber架构允许在 diff的时候不停的中断执行,所有render之前的声明周期可能会执行很多次。

fiber分为两部分

  • Reconciliation Phase
    • 进行diff,找出需要更新的DOM,这个过程是分片的。
  • Commit Phase
    • 更新渲染DOM,一气呵成。

js是单线程的,如果当前在执行一个很耗时的任务,那么剩下的任务就要等当前任务执行完之后再执行。16.x版本之前,React的更新过程是同步的,当React决定要更新DOM时,从diff到更新DOM,一气呵成。这种就会有一个问题,更新的组件比较复杂并且多(层级深等)的时候,此时如果用户点击了页面某个按钮,可能会因为正在批量更新DOM还未进行完成,按钮无法响应的问题。
fiber架构第一个阶段是分片的,将一个任务成很多个小的任务去执行,每次只执行一个小的任务,然后去看一下有没有优先级更高的任务,如果有,则去执行优先级更好的任务,如果没有,接着再执行下一小段任务。
为什么第二个阶段,更新渲染DOM必须是同步的呢,这个也很好理解。你总不能渲染了一半的时候去干其他的事情吧。

七. mixin、HOC、render props、hooks

mixin

vue和react中都曾用过mixin(react目前已经抛弃)
mixin(混入)本质上就是将对象复制到另一个对象上。

const mixin = function (obj, mixins) {
    const newObj = obj;
    newObj.prototype = Object.create(obj.prototype);
    
    for(let prop in mixins) {
        if(mixins.hasOwnProperty(prop)) {
            newObj.prototype[prop] = mixins[prop];
        }
    }
    return newObj;
}

const obj = {
    sayHello() {
        console.log('hello');
    }
};
const otherObj = function() {
    console.log('otherObj');
}
const Obj = mixin(otherObj, obj);

const a = new Obj(); // otherObj
a.sayHello(); //  hello

mixin存在的几个问题:

  • 相关依赖:mixin有可能去依赖其他的mixin,当我们修改其中一个的时候,可能会影响到其他的mixin
  • 命名冲突:不同的人在写的时候很有可能会有命名冲突,比如像 handleChange等类似常见的名字
  • 增加复杂性:当我们一个组件引入过多的mixin时,代码逻辑将会非常复杂,因为在不停的引入状态,和我们最初想的每个组件只做单一的功能背道而驰。

HOC

HOC是React社区提出的新的方式用来取代mixin的。
高阶函数是函数式编程中一个基本的概念,它描述了一种这样的函数:接受函数作为输入,或是返回一个函数,比如 map, reduce等都是高阶函数。
高阶组件( higher-order component),类似于高阶组件接受一个组件作为参数,返回另一个组件。

function getComponent(WrappedComponent) {
  return class extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>;
    }
  };
}

HOC的优点为:

  • 不会影响组件内部的状态

HOC的问题是:

  • 需要在原组件上进行包裹和嵌套,如果大量使用 HOC,将会产生非常多的嵌套,这让调试变得非常困难
  • HOC可以劫持props,在不遵守约定的情况下也可能造成冲突

Render Props

render props: 通过props接受一个返回react element 的函数,来动态决定自己要渲染的结果

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

React Router中就用到了 Render Props

<Router>
  <Route path="/home" render={() => <div>Home</div>} />
</Router>,

它有哪些问题呢

  • 很容易造成“嵌套地狱”

使用 hooks

具体实现就是通过一个函数来封装跟状态有关的逻辑,将这些逻辑从组件中抽取出来。而这个函数中我们可以使用其他的Hooks,也可以单独进行测试,甚至将它贡献给社区。

import { useState, useEffect } from 'react';

function useCount() {
  const [count, setCount] = useState(0);
  useEffect(() = {
    document.title = `You clicked ${count} times`;
  });
  
  return count
}

hooks的引入就是为了解决上面提到的这么问题,因为 使用函数式组件,我们在开发组件的时候,可以当做平常写函数一样自由。

函数复用是比较容易的,直接传不同的参数就可以渲染不同的组件,复杂组件实现,我们完全可以多封装几个函数,每个函数只单纯的负责一件事。而且很多公用的代码逻辑和一些场景我们可以抽出来,封装成自定义hooks使用,比如 Umi Hooks库封装了很多共用的逻辑,比如 useSearch,封装了异步搜索场景的逻辑;比如 useVirtualList,就封装了虚拟列表的逻辑。

八. immber和immutable

  • imutable是返回的包装数据
  • immer是原生数据

九.受控组件和非受控组件区别

受控组件和非受控组件区别

十一. vue和React区别

相同点

  1. 都支持服务器端渲染
  2. 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponent规范
  3. 数据驱动视图
  4. 都有支持native的方案,React的React native,Vue的weex

不同点

  1. React严格上只针对MVC的view层,Vue则是MVVM模式
  2. virtual DOM不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
  3. 组件写法不一样, React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'; Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,jd写在同一个文件;
  4. 数据绑定: vue实现了数据的双向绑定,react数据流动是单向的
  5. state对象在react应用中不可变的,需要使用setState方法更新状态; 在vue中,state对象不是必须的,数据由data属性在vue对象中管理

文章推荐

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions