分类
Javascript

React电商项目实战

本项目需求功能Vue shop相同,后端API也是一样的,使用的是React三件套:React、React Router和Redux。先对比一下最终效果吧,源码在这里下载。此文主要就这两个项目说一说React与Vue的区别。

一、渲染不同

React推荐通过JSX渲染模板。而Vue是更多时候和对HTML模板进行渲染。本质上,React是在组件JS代码中,通过原生JS语法比如插值、条件,循环等、来实现模板,更加纯粹。React通过js来操作一切:通过js来生成html,所以设计了jsx,甚至使用js来操作css。它自身的API很小,得自己实现,幸好React的社区很活跃,不用什么都造轮子。所以说,React的半门槛更高,更适合JS基础更好的开发工程师。

看下面CartControl组件中JSX模板代码:

render() {
    return (
      <div className="cart-control">
        <div className="cart-decrease" title="减"
          onClick={this.decrement}>
          <span className="iconfont icon-remove-circle"></span>
        </div>
        <input className="cart-count" value={this.state.count}
          onChange={this.handleChange} />
        <div className="cart-add" title="加" onClick={this.increment}>
          <span className="iconfont icon-add-circle"></span>
        </div>
      </div>
    )
  }
}

可以看到JSX模板其实就是React节点,作为render方法的返回值使用的。状态及方法更接近原生JS写法,绑定它们时仍然用到this。因为JSX的本质是js,此处的上下文就是组件本身。重要的事再强调一下,JSX的本质是js,得按JS的方式来处理。

需要注意的是:

react元素的事件处理和DOM元素的很相似,但是有一点语法上的不同:
1. React事件的命名采用小驼峰式(camelCase),而不是纯小写。
2. 使用JSX语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

Vue的单文件组件JS代码和HTML、CSS样式都是分离的,对模板的操作通过指令来实现的,比如条件语句就需要v-if来实现对这一点,Vue借鉴了angular的做法,因此更适合有设计基础的开发工程师。Vue自身的API很多,更像是一个是MVVM框架,有后端开发经验的同学理解起来也很容易。换名话说,Vue的学习曲线要平滑得多。

如CartControl组件中HTML模板代码:

<div class="cart-control">
  <div class="cart-decrease" title="减"
    @click.stop.prevent="decrement(product)">
    <span class="iconfont icon-remove-circle"></span>
  </div>
  <input class="cart-count" :value="count"
    @input="inputHandler"></input>
  <div class="cart-add" title="加" @click.stop.prevent="increment(product)">
    <span class="iconfont icon-add-circle"></span>
  </div>
</div>

注意到没有,这个模板用到了v-bind和v-on指令,使用的是简写方式,这些指令甚至有自己的修饰符。绑定状态及方法时不用上下this,在Vue的mount过程中,template会被编译成AST语法树。感兴趣的可以深入去了解一下。另外,借助transform-vue-jsx插件,Vue项目是也可以使用JSX。

二、组件写法不同

react是类式的写法,也可以使用函数组件。如下面的代码:

class CartControl extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: props.product.quantity || 1
    }
    this.handleChange = this.handleChange.bind(this)
    this.increment = this.increment.bind(this)
    this.decrement = this.decrement.bind(this)
  }

  static propTypes = {
    product: PropTypes.object.isRequired,
    added: PropTypes.number.isRequired,
    needConfirmed: PropTypes.bool
  }

  increment(e) {
    e.preventDefault()
    const { product, needConfirmed, added } = this.props
    let count = this.state.count;
    let max = count
    if (needConfirmed) {
      max = this.state.count + added;
    }
    if (max < product.inventory) {
      this.setState({ count: ++count });
      this.watchCount(count)
    }
  }
  ...
  render() {
    return (
      <div className="cart-control">
        <div className="cart-decrease" title="减"
          onClick={this.decrement}>
          <span className="iconfont icon-remove-circle"></span>
        </div>
        <input className="cart-count" value={this.state.count}
          onChange={this.handleChange} />
        <div className="cart-add" title="加" onClick={this.increment}>
          <span className="iconfont icon-add-circle"></span>
        </div>
      </div>
    )
  }
}

export default connect(state => state.cart, { willUpdateItem, updateCartItem })(CartControl)

就是es6的写法:类、方法、属性,很纯粹。

注意,事件方法一般要在类组件的构造函数中绑定,否则访问不到this。

而vue是声明式的写法,通过在字变量对象对象中设置mixins、directives、props、data、computed、生命周期方法、watch、methods自定义方法来定义组件。

export default {
  name: 'CartControl',
  props: {
    product: {
      type: Object,
      default: function(){}
    }
    ...
  },

  data() {
    return {
      count: this.product.quantity || 1
    }
  },

  watch: {
    count: function(count) {
      if (this.needConfirmed) {
        this.$store.commit('willUpdateCartItem', count)
      } else {
        this.$store.commit('updateCartItem', {
            id: this.product._id,
            count: count
          }
        )
      }
    }
  },

  methods: {
    increment() {
      let max = this.count
      if (this.needConfirmed) {
        max = this.count + this.added;
      }
      if (max < this.product.inventory) {
        this.count++
      }
    }
    ...
}
</script>

所以react组件很纯粹,和typescript整合更容易,vue整合起来稍微复杂。

三、数据流的不同

React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不能对修改props,如图所示:

Vue也提倡单向数据流,但通过向父组件emit一个事件,或者借助v-model实现父子组件双向绑定了,这对于一些特殊场景,比如处理表表单也是很有用的。另外,新版的Vue也不鼓励组件对自己的props进行任何修改了。如下图:

四、监听数据变化的实现不同

Vue将遍历此data对象所有的property,并使用 Object.defineProperty通过 getter/setter以及一些函数的劫持追踪依赖。详见深入响应式原理。正是因为Vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树,因此可以更快地计算出VDOM差异。

React默认是通过比较引用的方式(diff)进行的,在应用的状态被改变时,全部子组件都会重新渲染。它是为大项目而生的,如果不优化可能导致大量不必要的VDOM的重新渲染,甚至还没有直接操作DOM来得快。

Vue的实现简单很多,Vue使用的是可变数据,可以对data对象随意修改。而React更强调数据的不可变,对状态的修改必须要用setState方法。

React是这样修改的:

handleChange(e) {
  let val = e.target.value
  // Should be a positive integer
  if (/^[1-9]\d*$/.test(val) && val <= this.props.product.inventory) {
    this.setState({ count: parseInt(val) })
    this.watchCount(val)
  } else {
    e.target.value = this.state.count
  }
}

vue是这样实现的,

inputHandler(e) {
  let val = e.target.value
  // Should be a positive integer
  if (/^[1-9]\d*$/.test(val) && val < this.product.inventory) {
    this.count = parseInt(val)
  } else {
    e.target.value = this.count
  }
}

五、Redux和Vuex的联系区别

本质上,Redux和Vuex都是对MVVM思想的服务,将数据从视图中抽离的一种方案。它们都使用单一的数据源,用了设计模式中的观察者模式或者说发布/订阅模式。在React中,initState相当于event对象,dispatch相当于trigger方法, dispatch的函数则相当于订阅的事件。在Vuex中,$store相当于event对象,store.commit相当于trigger方法, mutation中的函数名则相当于订阅的事件。Vuex借鉴了Redux,将store作为全局的数据中心进行数据管理。

Redux数据流向:
View –> Actions –> Reducer –> State变化 –> View变化(同步异步一样)

Vuex的数据流向:

View –> Commit –> Mutations –> State变化 –> View变化(同步操作)
View –> Dispatch –> Actions –> Mutations –> State变化 –> View变化(异步操作)

1. 在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。在Vuex中,$store被直接注入到了组件实例中,因此可以比较灵活的使用;
2. Vuex改进了Redux中的Action和Reducer函数,以Mutations变化函数取代Reducer,无需switch,只需在对应的Mutation函数里改变state值就可以;
3. Vuex只能配合Vue使用,Redux可以整合到任何框架中;
4. 而Redux中没有异步操作的API,得借助redux-thunk来实现,Vuex中可以直接使用Action进行异步操作;
5. 而Redux是没有module化功能,Vuex自带就有。

React和Vue中的路由基本上是一样,Vue Router借鉴了React Router,核心组件、配置和API都差不多。

React和Vue的设计思想是不一样的,前者是映射思维,后者是模板思维。在映射思维的世界观里,js数据是唯一的实质。在模板思维看来,html模板和js数据都是实质,二者是并列的关系;