Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
funnycoderstar opened this issue Apr 13, 2020 · 0 comments
Open

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

funnycoderstar opened this issue Apr 13, 2020 · 0 comments
Labels

Comments

@funnycoderstar
Copy link
Owner

funnycoderstar commented Apr 13, 2020

目录

  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对象中管理

文章推荐

@funnycoderstar funnycoderstar changed the title React知识点整理 【面试题】React知识点整理(附答案) Apr 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant