WIP:Vue、React对比

 

生命周期对比

先附上二者的生命周期图

Vue

  • beforeCreate 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  • created 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event
    事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
  • beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • activated keep-alive 组件激活时调用。
  • deactivated keep-alive 组件停用时调用。
  • beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用
  • destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • errorCaptured v2.5+ 当捕获一个来自子孙组件的错误时被调用

Vue 实例生命周期

React 16.2

React 16.3

官方文档

deprecated:

  • componentWillMount
  • componentWillReceiveProps(nextProps)
  • componentWillUpdate(nextProps, nextState)

React 16.4

对比图:

图中可以对应上的回调函数用相同颜色标出。

  • Mounting阶段:基本一致。都有初始化和mount前后的回调。
  • Unmounting阶段:基本一致。Vue多一个unmount前的回调。
  • Updating阶段:二者都有update前后的回调。而React提供了shouldComponentUpdate用于优化渲染。而Vue通过依赖收集可以精准更新,所以并不需要这个回调。在16.3以后,componentWillMount、componentWilReceiveProps、componentWillUpdate都被废弃了。

props vs propTypes

Vue Component Propertis

Typechecking with PropTypes

Vue的props和methods,components一样都是通过new Vue(options)的options配置的。

React是通过自定义Component上的静态属性propTypes定义的。

更新DOM对比

React Conciliation

React对旧组件树与state、props变化引起生成的新组件树进行对比。这个具体的对比并更新的算法就是Conciliation算法。

其中根组件类型发生变化引起整棵树重新渲染;以及key属性对渲染优化起到的作用是需要程序员关注的。

React通过且仅通过React.Component.prototype.setState来更改组件状态。但是这个setState有一些特别关注的点:

/**
 * Sets a subset of the state. Always use this to mutate
 * state. You should treat `this.state` as immutable.
 *
 * There is no guarantee that `this.state` will be immediately updated, so
 * accessing `this.state` after calling this method may return the old value.
 *
 * There is no guarantee that calls to `setState` will run synchronously,
 * as they may eventually be batched together.  You can provide an optional
 * callback that will be executed when the call to setState is actually
 * completed.
 *
 * When a function is provided to setState, it will be called at some point in
 * the future (not synchronously). It will be called with the up to date
 * component arguments (state, props, context). These values can be different
 * from this.* because your function may be called after receiveProps but before
 * shouldComponentUpdate, and this new state, props, and context will not yet be
 * assigned to this.
 *
 * @param {object|function} partialState Next partial state or function to
 *        produce next partial state to be merged with current state.
 * @param {?function} callback Called after state is updated.
 * @final
 * @protected
 */
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

源码中说,setState不保证是同步更新state,多个setState可能会被batched together。所以这个函数是不是应该叫做setStateAsync呢。也不是,因为在setTimeout和addEventListener中的函数调用setState确是同步的。

从上面代码中的注释可以看出

* @param {object|function} partialState Next partial state or function to
*        produce next partial state to be merged with current state.
* @param {?function} callback Called after state is updated.

setState的第一个参数可以是一个新state,也可以是一个函数返回新state。第二个参数是一个可选的回调函数。在state更新后被调用。

Vue Dependencies Collection

Vue在构建渲染树的过程中收集『依赖关系』并在必要的时候自动更新相应的元素。同样在渲染列表元素时也提供通过key来优化渲染的途径。

 

各种Component

Functional Components

也叫Stateless Components 、Dumb Components、Presentational Components。 没有生命周期,只接受props然后进行渲染。Vue关于Functional Component的文档。Vue需要用functional来标识。React只需定义一个函数返回JSX即可。

PureComponent

这个是React的概念。通过shallowEqual比较前后状态是否发生变化实现了shouldComponentUpdate。只要shallowEqual为真则认为不需要更新组件。在下面checkShouldComponentUpdate函数中,如果组件自定义了shouldComponentUpdate 则调用之。如果没有,则检查组件是否是一个PureComponent,如果是,则进行shallowEqual来确定是否需要更新组件。

function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  const instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    stopPhaseTimer();

    if (__DEV__) {
      warningWithoutStack(
        shouldUpdate !== undefined,
        '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
          'boolean value. Make sure to return true or false.',
        getComponentName(ctor) || 'Component',
      );
    }

    return shouldUpdate;
  }

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}

Class Component

有完整的生命周期,state,可以访问store

在组件树中共享数据

Vue’s provide/inject 和 React Context API

React先有的Context API,然后有人提了Issue#4929后,Vue在f916bcf添加了provide/inject

二者都是为了解决:“在组件树中共享数据的问题”。

React官方文档:Context is designed to share data that can be considered “global” for a tree of React components, 
such as the current authenticated user, theme, or preferred language.
Vue官方文档:This pair of options are used together to allow an ancestor component to serve as a dependency injector for all its descendants, 
regardless of how deep the component hierarchy is, as long as they are in the same parent chain. 
If you are familiar with React, this is very similar to React’s context feature.

 

Redux vs Vuex

Redux的设计原则

  • Single source of truth
  • State is immutable
  • Changes are made with pure functions

组成:

  • View:dispatch actions
  • Action: 描述发生了什么
  • Reducer:变换State
  • Store:保存整个应用的state

加上middleware

 

Vuex

  • state:驱动应用的数据源
  • view:以声明方式将state映射到view
  • actions:响应view上用户的输入导致的状态变化

使用对比

官方react-redux通过 ProviderContextconnect实现binding。

Provider

import React from "react";
import ReactDOM from "react-dom";

import { Provider } from "react-redux";
import store from "./store";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

connect, mapDispatchToProps, mapStateToProps

import { connect } from "react-redux";
import { increment, decrement, reset } from "./actionCreators";

// const Counter = ...

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    counter: state.counter
  };
};

const mapDispatchToProps = { increment, decrement, reset };

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

Vue和vue集合的很紧密,通过getter和mapGetter将store中的数据与组件连接起来

import user from './user.store'
import children from './children.store'

const store = new Vuex.Store({
  modules: {
    user,
    children
  }
})

new Vue({
  store,
  computed: {
    ...mapGetters({
      userInfo:  'userInfo',
      children: 'children'
  }),
})

其他方面的对比

  • Redux与React完全分离,通过react-redux结合使用。而Vuex中直接引用Vue, 利用Vue的Reactive系统实现了核心功能。所以无法单独使用。
  • Redux使用单一store,reducer分散在不同地方,然后通过redux.combineReducers组合起来;Vuex将store分割为多个module,然后new Vuex.Store({modules:{...}})将多个module组合起来

更新:虽然无法单独使用Vuex。但是。你可以在服务端代码中使用Vuex+Vue

Slots vs Component-as-prop

Vue的Slot提供向组件内部插入其他组件的能力:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>

React可以通过Component as prop的方式实现,将组件以prop传入然后直接渲染出来:

<Layout
  left={<Sidebar/>}
  top={<NavBar/>}
  center={<Content/>}
/>
unction Layout(props) {
  return (
    <div className="layout">
      <div className="top">{props.top}</div>
      <div className="left">{props.left}</div>
      <div className="center">{props.center}</div>
    </div>
  );
}

 

扩展组件的方式对比

Vue官方文档:Adding Instance Properties

直接在Vue.prototype上添加以$开头的属性和方法,就可以为所有Vue实例添加方法。

Vue.prototype.$http = axios;
new Vue({
  el: '#app',
  data: {
    users: []
  },
  created() {
    var vm = this
    this.$http
      .get('https://jsonplaceholder.typicode.com/users')
      .then(function(response) {
        vm.users = response.data
      })
  }
})

React当然也可以通过给Componen.prototype添加属性和方法达到上述目的。但是官方并没有这样的文档。通常做法是通过高阶组件修改props:

class Comp extends React.Compoent {
  //...
}

function Log (Comp) {
  return props => {
    const extentedProps = {...props,
      log: console.log
    }
    return <Comp {...extentedProps}/>
  }
}

export default Log(Comp)

 

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top