Skip to content
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

Review-Question-React #7

Open
Leo-lin214 opened this issue Dec 17, 2019 · 0 comments
Open

Review-Question-React #7

Leo-lin214 opened this issue Dec 17, 2019 · 0 comments

Comments

@Leo-lin214
Copy link
Owner

  1. 说说react的virtual dom及其diff的实现方式
  2. 说一说react的生命周期
  3. react中组件有哪几种类型?那么创建组件又有哪几种方式?
  4. 在react和vue中,组件上写上key的作用是什么?
  5. 聊聊setState机制
  6. 谈谈对react批量更新(batchUpdates)理解?
  7. react如何区别component和dom?
  8. 说说react组件间的通信方式都有哪些
  9. 如何将一个子组件单独渲染出来和父组件平级关系?
  10. react组件都有哪些优化手段?
  11. 说说react的事件机制
  12. 有了解过Fiber吗?
  13. 谈谈高阶组件和基类
  14. 谈谈React.Component、React.PureComponent和无状态组件间区别
  15. react-router的实现以及原理
  16. redux和vuex的设计思想有什么异同?
  17. redux为什么要把reducer设计成纯函数?
  18. 了解过redux-saga和dva吗?

说说react的virtual dom及其diff的实现方式

当下react中的virtual dom主要使用Fiber架构实现,核心包括fiber数据结构以及调度器。

fiber数据结构可以说是一种升级版virtual dom,采用的是链表的方式,其中child属性指向子fiber对象,return指向父fiber对象,sibling指向兄弟fiber对象,并且真实DOM结构中每一个节点都对应一个fiber对象。

所谓调度器,主要处理两件事情,根据任务的优先级计算好过期时间以及采用requestIdleCallback方法实现调度。其中过期时间的计算是当前时间 + 优先级,实现的requestIdleCallback则是使用requestAnimationFramesetTimeout实现。原理就是,将整个更新任务拆分成一个个小的任务,并且可控制这些任务的执行,高优先级的会比低优先级的优先执行,而且优先级高的还可以中断优先级低的

至于diff的实现,原理上和Vue实现的一致,可观看这篇文章 【Vue 源码分析 】如何在更新 Patch 中进行 Diff


说一说react的生命周期

react 16版本之前,生命周期主要分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。

  1. 挂载阶段
    • constructor
    • componentWillMount
    • render
    • componentDidMount
  2. 更新阶段
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  3. 卸载阶段
    • componentWillUnmount

react 16版本开始,已经抛弃了componentWillReceivePropscomponentWillMountcomponentWillUpdate,引入了getDerivedStateFromPropsgetSnapshotBeforeUpdatecomponentDidCatch

  1. 挂载阶段
    • constructor
    • static getDerivedStateFromProps
    • render
    • componentDidMount
  2. 更新阶段
    • static getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  3. 卸载阶段
    • componentWillUnmount

getDerivedStateFromProps:静态方法,存在目的是让组件在props变化时更新state与组件本身无相关,无法直接访问组件上任何数据。触发机制是组件本身任何情况下的更新都会触发该回调函数(官方不推荐使用)。官方替代方案主要有两种:让组件变成完全受控组件和让组件变成不完全受控组件结合key属性(key值变化时,React会重新创建组件而非直接更新)。返回null则说明不需要更新state。

getSnapshotBeforeUpdate:在组件update之前发生,返回一个值,作为componentDidUpdate第三个参数。

若需要根据props更新后直接请求后段获取数据,改用componentDidUpdate,参数为prevPropsprevStatesnapshot,可直接根据prevProps与现在props判断该次更新是否为props更新导致。


react中组件有哪几种类型?那么创建组件又有哪几种方式?

react中组件类型有三种,分别是无状态组件、普通组件、PureComponent

创建组件方式有函数式定义无状态组件、ES5中使用React.createClass创建、ES6使用class extends React.Component创建。


在react和vue中,组件上写上key的作用是什么?

由于reactvue采用的都是diff策略,而key的作用则可以在新老节点对比时准确地判断该节点是否需要重新渲染,避免了不必要的渲染从而提升性能。常常应用于列表以及组件,一旦key值变更,将直接重新创建该元素或组件而不是直接更新。


聊聊setState机制

首先,setState的处理采取的是队列机制完成更新。当调用setState时,会将需要更新的state浅合并后放入状态队列,而不会立即更新state,队列机制会采取批量更新的形式对state进行更新。(当然为了处理这种情况,可以使用function作为参数模拟数据合并)

先看下setState入更新队列源码。

function enqueueUpdate(component) {
  // ...
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStaretegy.batchedUpdates(enqueueUpdate, component)
    return
  }
  dirtyComponents.push(component)
}
// 先简单看看batchingStrategy对象
var batchingStrategy = {
  isBatchingUpdates: false,

  batchedUpdates: function(callback, a, b, c, d, e) {
    // ...
    batchingStrategy.isBatchingUpdates = true;
    
    transaction.perform(callback, null, a, b, c, d, e);
  }
}

简单滴讲,在生命周期调用setState前,其实就已经进入了enqueueUpdate方法并且设置isBatchingUpdatestrue。因此当直接调用setState时直接丢进了dirtyComponent中而不会立马更新处理。

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

至于setState机制,简单总结便是,React会将任务全部放进一个队列中,根据isBatchingUpdates标记判断是否同步更新state状态,默认为false,表示setState会同步更新状态。实现上,只有React中引发的事件处理过程才会异步处理,其他处理都会同步处理

详细可以看看这里https://github.com/sisterAn/blog/issues/26。


谈谈对react批量更新(batchUpdates)理解?

直接参考一下从源码全面剖析 React 组件更新机制


react如何区别component和dom?

使用ReactDomrender方法可以确认。根据在render方法中传入的第一个参数来确定,当传入的是jsx时,就会使用React.createElement来创建相应的虚拟DOM,只有当传入的是函数式组件或class组件就会认为这就是一个组件。


说说react组件间的通信方式都有哪些

  1. 父向子间通信

    使用props传递数据。

  2. 子向父间通信

    使用props传递的callback

  3. 跨级间通信

    使用context通信。

  4. 无任何嵌套关系间通信

    使用EventEmitter自定义事件机制。

    使用Redux数据管理。


如何将一个子组件单独渲染出来和父组件平级关系?

  1. 使用unstable_renderSubtreeIntoContainer实现

    export default class A extends Component {
      componentDidMount() {
        this.parentElement = ReactDOM.findDOMNode(this).parentElement;
        this.renderChild();
      }
      componentDidUpdate() {
        this.renderChild();
      }
      renderChild() {
        const renderACom = (
          <div>
            {this.render()}
            {this.props.children}
          </div>
        )
        ReactDOM.unstable_renderSubtreeIntoContainer(
          this, renderACom, this.parentElement
        );
      }
      render() {
        return <div>a组件</div>;
      }
    }
  2. 使用createPortal实现

    export default class A extends Component {
      componentDidMount() {
        this.forceUpdate();
      }
      render() {
        const renderACom = (
          <div>
            <div>a组件</div>
            {this.props.children}
          </div>
        )
        if (!ReactDOM.findDOMNode(this)) {
          return <div></div>;
        }
        return ReactDOM.createPortal(
          renderACom, ReactDOM.findDOMNode(this).parentElement
        );
      }
    }

react组件都有哪些优化手段?

  1. 尽量多滴使用函数式组件或PureComponent
  2. 对一些不必要的重渲,需要使用shouldComponentUpdate生命周期进行过滤。
  3. 运用immutable。
  4. 对组件拆分要有一个把控,要考虑好可控组件以及不可控组件。

说说react的事件机制

react基于 Virtual DOM 实现了一个 SyntheticEvent(合成事件)层,组件中定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,与原生的浏览器事件一样拥有同样的接口,同样支持事件的冒泡机制

它与原生事件区别主要在于驼峰规则、处理对象(合成事件处理的是函数,而原生事件处理的却是字符串)。

另外,合成事件的实现中采用的是事件代理机制。不会把处理函数直接绑定到真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,该事件监听器维持了一个映射来保存所有组件内部的事件监听和处理函数。当事件发生时,首先被该统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用,当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象。


有了解过Fiber吗?

Fiber架构可以说是react在虚拟DOM上的一个重构。主要包含的内容就是fiber数据结构和调度器。处理的目标是针对动画、布局和手势。

通过将渲染任务进行拆分小任务,并根据各自的优先级计算好过期时间,采用增量渲染的方式,每次只执行一小段渲染任务,然后把任务放回给主线程,这样就可以很好滴避免由于长时间渲染而导致主线程被阻塞。

有兴趣的童鞋可以看看这位童鞋的分享,我觉得真的讲的很好。这可能是最通俗的 React Fiber(时间分片) 打开方式


谈谈高阶组件和基类

高阶组件是将组件作为参数,通过包裹的形式,为传入的组件增加功能并返回一个新的react组件。

基类是可为子类继承方法或属性的一个类。

react中推崇的是组合而非继承,因此常常推荐使用的是高阶组件。其中高阶组件常用地方主要体现在属性代理组件、反向继承组件以及组合多个高阶组件


谈谈React.Component、React.PureComponent和无状态组件间区别

React.Component作为一个常见组件写法,可拥有单独的状态state以及生命周期,但有一个缺陷就是,父组件更新即使传入的props数据不变,也会直接影响该组件重新渲染。

React.PureComponent就是在React.Component上的一个升级,使用生命周期shouldComponentUpdate对传入数据props进行浅层比较,若无变化就不会重新渲染该组件,其他行为和React.Component一致。

无状态组件只接受数据props的传入,不会存在任何组件内部状态state以及生命周期,只负责UI的渲染,因此更多推荐的是使用无状态组件。


react-router的实现以及原理

先说下实现,react-router根据环境的不同采取不同的实现,主要分为三种情况,分别是:

  1. 老版本浏览器的history实现。通过hash实现,对应createHashHistory方法。
  2. 高版本浏览器实现。通过h5里的history实现,对应createBrowserHistory方法。
  3. Node环境下实现。通过存储在memory里实现,对应createMemoryHistory方法。

接着说下hashhistory之间区别。

  • hash模式:hash会被包含在URL中,不会被包含在http请求中,对后端完全没影响,即改变hash不会重新加载页面。另外**hash模式原理是onhashchange事件**。
  • history模式:刚好相反,history模式会去直接请求接口获取页面。history模式则是通过popState事件进行监听

react-router基本原理是:URL对应Location对象,而UI则由React.Component决定,直接将Locationcomponent之间的同步问题


redux和vuex的设计思想有什么异同?

共同点: 两者都是作为全局状态管理的工具库,思想都是差不多:

  • 使用state存储状态。
  • 使用dispatch分发action触发数据的变更。
  • redux使用reducer统一管理数据变化的操作,而vuex中则是用mutation管理。
  • 使用getter形式获取状态。

区别:

  • 在处理异步情况下会有所不同,redux采用中间件形式,而vuex则使用在commit前直接处理异步。
  • 状态变更时,redux不会直接更改原数据,对于引用类型采取新建方式,而vuex则是直接修改状态属性来触发更新操作。

redux为什么要把reducer设计成纯函数?

首先针对的是引用类型,由于redux在源码的实现上对于引用类型只会进行浅比较,我们都知道深比较是很耗性能的。

所以若直接修改原数据返回一个同引用地址的对象(即只是更改它内容),在react会认为是一个没有人变化对象,就会导致无法触发重新渲染。


了解过redux-saga和dva吗?

redux-saga就是用于管理副作用如异步获取数据,访问浏览器缓存的一个中间件。其中reducer负责处理state更新,sagas负责协调异步操作。

dva是阿里体验技术开发的react应用框架,主要用于解决组件之间通信问题。在引入redux后,又由于redux没有异步操作,又需要引入redux-saga等插件,才有了dva的出现。

dva = React-Router + Redux + Redux-saga

dva的最简结构:

import dva from 'dva';
const App = () => <div>Hello dva</div>;

// 创建应用
const app = dva();
app.model(model)
// 注册视图
app.router(() => <App />);
// 启动应用
app.start('#root');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant