Vue 和 React 都是目前最流行, 生态最好的前端框架之一, 之所以用 "与" 字来做标题, 也是为了避免把他们放在对立面. 毕竟框架本身没有优劣之分, 只有适用之别, 选择符合自身业务场景, 团队基础的技术才是我们最主要的目的.
本文希望通过对比两个框架在使用上的区别, 能使只用其中一个框架进行开发的开发者快速了解和运用另一个框架, 已应对不同技术栈的需求, 无论你是为了维护老系统还是为了适应新的生态(React-Native).
那我们现在就从下面这几个步骤来分别聊聊 Vue 和 React 的区别:
1. 如何开始第一步?
Vue 和 React 分别都有自己的脚手架工具: vue-cli 和 create-react-App, 两者都能帮助我们快速建立本地的开发环境, 具体步骤如下:
- vue-cli 3.*
- NPM install -g @vue/cli
- vue create my-project //node 版本推荐 8.11.0+
- // 如果你还是习惯之前 2.* 版本的话, 再安装一个工具即可
- NPM install -g @vue/cli-init
- vue init webpack my-project
生成的项目结构大同小异, 只不过 3.* 版本将原先直接暴露出来的 webpack 配置封装到了自己的 vue.config.JS 配置文件中, 包括常见 baseUrl,outputDir,devServer.proxy 等.
Vue-2:
Vue-3:
- create-react-App
- NPM install -g create-react-App
- npx create-react-App my-project
可以看出 create-react-App 的封装做的更为彻底一些, 基本将所有的 webpack 配置都隐藏了起来, 仅由 package.JSON 来控制. 但对于需要灵活配置的场景, 这样的封装显然是不合适的, 你可以运行 NPM run eject 将封装的配置再次释放出来.
2. 如何解决最基本的开发需求?
使用脚手架我们可以非常顺利的搭建起本地的开发环境, 那下一步就是我们将使用它们来进行业务开发. 在此之前, 我们可以先问一个问题, 什么是最基本的开发需求?
我们可以简单总结为三个方面, 而面对新框架你也可以从这三个方面入手, 看框架是如何处理的, 从而达到快速上手的目的.
模板渲染: 将从后端获取的数据 (或者是已经在 JS 中的数据) 根据一定的规则渲染到 DOM 中.
事件监听: 监听用户的操作行为, 例如点击, 移动, 输入等.
处理表单: 将用户操作行为所产生的数据影响保存下来, 并发送给后端, 处理返回结果.
我们先来看 Vue 是如何处理这三方面的
- // 模板渲染, 可以直接使用 data 对象中的数据, 利用指令来处理渲染逻辑
- <template>
- <div class="hello">
- <div v-for="(item, index) in list" :key="index">
- {{ item.title }}
- </div>
- // 处理表单, 将用户输入的内容赋值到 title 上
- <input v-model="title" />
- // 事件监听, 用户点击后触发 methods 中的方法
- <button @click="submit">提交</button>
- </div>
- </template>
- <script>
- export default {
- data () {
- return {
- list: [
- { title: 'first' },
- { title: 'second' }
- ],
- title: ''
- }
- },
- methods: {
- submit () {
- this.list.push({ title: this.title })
- this.title = ''
- }
- }
- }
- </script>
而 React 的书写方式为:
- import React, { Component } from 'react';
- export default class HelloWorld extends Component {
- state = {
- list: [
- { title: 'first' },
- { title: 'second' },
- ],
- title: '',
- };
- setTitle = (e) => {
- // 需要手动调用 setState 来进行重绘, 否则 input 的 value 不会改变
- this.setState({ title: e.target.value })
- }
- submit = (e) => {
- const { title, list } = this.state;
- list.push({ title });
- this.setState({ list, title: '' });
- }
- render() {
- const { title, list } = this.state;
- return (
- <div className="App">
- // react 会使用 jsx 的方式来进行模板的渲染, 可混合使用 JS 和 HTML 标签
- // {}解析 JS,()解析 HTML
- { list.map((item, index) => (
- <div key={index}>{ item.title }</div>
- )
- )}
- // 事件监听 + 表单处理
- <input value={title} onChange={this.setTitle} />
- <button onClick={this.submit}>增加</button>
- </div>
- );
- }
- }
从上面两个例子中我们可以看出, Vue 的写法稍微简单一下, 不需要手动利用 setState 这样类似的特定函数来触发数据与模板的同步. 但也正由于手动触发, React 显得更加自由一些, 我们可以根据实际情况去处理, 适当减少不必要的重绘, 从而对性能进行优化.
3. 生命周期有什么不一样?
生命周期一直是 Vue 和 React 中非常重要的概念, 明确的表达了组件从创建, 挂载, 更新, 销毁的各个过程. 那两者在细节上有什么区别呢, 我们可以通过它们各自官网的图来看一下.
Vue 的生命周期如下图:
React 的生命周期如下图:
主体流程大致都可以分为:
创建组件: 建立组件状态等一些前置工作, 如果需要请求数据的话, 大部分在此时进行.
挂载组件: 将组件真正挂载到 Document 中, 可以获取真实的 DOM 进行操作.
更新组件: 组件自身的状态, 外部接受的参数发生变化时.
卸载组件: 当组件要被移除当前 Document 时, 通常会销毁一些定时器等脱离于组件之外的事件.
4. Vue 比 React 多了哪些常见属性
Vue 之所以给人上手简单, 开发便捷的感觉, 有一部分原因就在于封装了很多常见的操作, 你只需要简单的使用一些属性就可以达到目的. 便捷和自由有的时候就是一对反义词, 你在获得一些特性的时候就会牺牲一些特性, 就像算法上常见的空间换时间这样的策略.
那我们就来看看 Vue 的哪些常见属性是 React 中没有的:
computed
计算属性经常用于集中管理多个状态的结果, 避免将过多的计算逻辑暴露在模板或其他引用的地方. 我们也无需知道该计算属性依赖的属性何时, 如何变化, Vue 能自动监听变化结果并重新计算. 例如:
- computed: {
- fullName () {
- return this.firstName + ' ' + this.lastName;
- }
- }
- ####watch: 和计算属性类似, 可以指定监听某个状态变化, 并获得该属性变化前后的值. 例如:
- watch: {
- name (val, oldVal) { // 监听某个属性发生变化
- ....
- }
- }
而在 React 中, 你需要实现这一功能, 你可能需要在 componentWillReceiveProps 时自己去判断属性是否发生了变化, 或者在 setState 的同时触发变化后的业务逻辑. 例如:
- componentWillReceiveProps(nextProps) {
- if (nextProps.name != this.props.name) { // props 中的某个属性发生了变化
- ....
- }
- }
- #### 指令 Vue 中的指令主要用于封装对 DOM 的操作, 例如模板中的 v-if/v-else/v-for 等; React 本身利用 jsx 来操作 DOM, 所以也就没有这一概念. 我们举一个自定义指令的例子, 来提现下指令的作用:
- <img v-lazy="img_url" />
- directives: {
- 'lazy': {
- inserted: function (el, binding) {
- var body = document.body;
- var offsetTop = el.offsetTop;
- var parent = el.offsetParent;
- // 获取绑定元素对于 body 顶部的距离
- while (parent && parent.tagName != 'body') {
- offsetTop += parent.offsetTop;
- parent = parent.offsetParent;
- }
- // 若出现在可视区域内, 则直接赋值 src
- if (body.scrollTop + body.clientHeight> offsetTop && body.scrollTop <offsetTop) {
- el.src = binding.value;
- } else {
- // 若暂未出现, 则监听 Windows 的 scroll 事件
- var scrollFn = function () {
- // 出现在区域内才赋值 src, 并取消事件监听
- if (body.scrollTop + body.clientHeight> offsetTop && body.scrollTop <offsetTop) {
- el.src = binding.value;
- Windows.removeEventListener('scroll', scrollFn)
- }
- }
- Windows.addEventListener('scroll', scrollFn)
- }
- }
- }
- }
这里其实我们也可以试想一下, 如果使用 React 的话, 我们可以如何实现图片懒加载这个功能?
5. 组件的区别
组件目前可以说是两个框架开发中最基本的单位, 每个项目都包含一个根组件, 然后再以路由进行细分, 从页面直到功能更为单一的组件. 而如何处理项目中多个组件的关系, 如何高效的复用组件也就成了框架所需要考虑的事情, 下面就和大家对比下两者在处理这种情况下的异同.
组件通信
组件通信是组件关系中最常见的一种, 主要表现在父组件向子组件传递数据, 子组件调用父组件方法影响父组件. 我们分别举例来说明两者在写法上的区别:
Vue 的写法:
- // 父组件
- <template>
- <div class="parent">
- <div v-for="(msg, index) in msgs" :key="index">
- {{ msg.content }}
- </div>
- // 通过 v-bind 可以绑定父组件状态传递给子组件
- // 并且可以自定义事件将方法传递给子组件
- <child :last="last" @add="add"></child>
- </div>
- </template>
- <script>
- import Child from '@/components/PropsEventChild'
- export default {
- components: {
- Child
- },
- data () {
- return {
- name: 'parent',
- msgs: []
- }
- },
- computed: {
- last () {
- const { msgs } = this
- return msgs.length ? msgs[msgs.length - 1] : undefined
- }
- },
- methods: {
- add (msg) {
- this.msgs.push(msg)
- }
- }
- }
- </script>
- // 子组件
- <template>
- <div class="child">
- <input v-model="content" placeholder="请输入" />
- <button @click="submit">提交</button>
- </div>
- </template>
- <script>
- export default {
- // 此处需要定义接受参数的名称
- props: ['last'],
- data () {
- return {
- content: ''
- }
- },
- methods: {
- submit () {
- const time = new Date().getTime()
- const { last } = this
- if (last && (time - last.time <10 * 1000)) {
- alert('你发言太快了')
- return
- }
- // 通过 $emit 的方式可以调用父组件传递过来的自定义事件, 从而修改父组件的状态
- this.$emit('add', { content: this.content, time })
- this.content = ''
- }
- }
- }
- </script>
React 写法:
- // 父组件
- import React, { Component } from 'react';
- import Child from './Child'
- export default class Parent extends Component {
- state = {
- msgs: []
- };
- get last () {
- const { msgs } = this.state;
- return msgs.length ? msgs[msgs.length - 1] : undefined;
- }
- add = (msg) => {
- const { msgs } = this.state;
- msgs.push(msg);
- this.setState({ msgs });
- };
- render() {
- const { msgs } = this.state;
- return (
- <div className="Parent">
- { msgs.map((item, index) => (
- <div key={index}>{ item.content }</div>
- )
- )}
- // 直接传递参数和方法
- <Child last={this.last} onClick={this.add}/>
- </div>
- );
- }
- }
- // 子组件
- import React, { Component } from 'react';
- export default class Child extends Component {
- state = {
- content: ''
- };
- setContent = (e) => {
- this.setState({ content: e.target.value })
- }
- submit = (e) => {
- const { props: { last }, state: { content } } = this
- const time = new Date().getTime()
- if (last && (time - last.time <10 * 1000)) {
- alert('你发言太快了')
- return
- }
- // 直接调用传递过来的方法
- this.props.onClick({ content, time })
- this.setState({ content: '' })
- }
- render() {
- const { content } = this.state;
- return (
- <div className="Child">
- <input value={content} onChange={this.setContent} />
- <button onClick={this.submit}>增加</button>
- </div>
- );
- }
- }
组件嵌套
实际开发当中经常会使用到一些提供单纯的 UI 功能, 但内容需要业务自行定义的组件, 例如 Modal,Table 等. 这种类型的组件往往和会业务组件进行嵌套, 但不影响业务组件的状态. Vue 提供了这样写法, 可以将业务组件替换掉 UI 组件中特定的一部分, 这里就不赘述代码了, 直接上一下例子:
Vue Slot 组件
Vue Slot 使用方式
React 中没有 < slot > 这种语法, 但组件本身可以将标签内的部分以 props.children 的方式传递给子组件, 例如:
- import React, { Component } from 'react';
- import Wrapper from './Wrapper'
- export default class Demo extends Component {
- state = {
- content: '我是 Demo 组件中的内容'
- };
- render() {
- return (
- <div>
- <Wrapper>
- <div>{ this.state.content }</div>
- </Wrapper>
- </div>
- );
- }
- }
- import React, { Component } from 'react';
- export default class Wrapper extends Component {
- render() {
- return (
- <section>
- <header > 我是 Wrapper 头部</header>
- { this.props.children }
- <footer > 我是 Wrapper 尾部</footer>
- </section>
- );
- }
- }
可以看出 this.props.children 代表的就是父组件 (class Demo) 中的被包裹的标签 < div>{ this.state.content }</div>
无状态组件
有的时候我们可能希望组件只是起一个渲染的作用, 并不需要状态变化或者生命周期这种监听. 为了节约性能, React 可以只使用函数来接受和使用 props:
- const Wrapper = (props) => (
- <section>
- <header > 我是 Wrapper 头部</header>
- { props.children }
- <footer > 我是 Wrapper 尾部</footer>
- </section>
- )
Vue 当中有种类似的用法, 在 template 标签中加上 functional, 模板渲染后不会对数据进行监听:
- <template functinal>
- <section>
- <header > 我是 Wrapper 头部</header>
- { props.children }
- <footer > 我是 Wrapper 尾部</footer>
- </section>
- </template>
- // 除了 template 标签之外, 该组件已无法使用 Vue 实例, 也就是不能在该. vue 文件中使用 < script></script > 标签.
跨组件通信
所谓的跨组件通信只指不通过 props 传递获取父组件中定义的数据, 这样深层的子组件就能直接访问到顶层的数据.
React 中提供了 Context 这样的机制来实现, 具体代码:
定义 Context
设定 Provider, 定义数据
设定 Consumer, 获取数据
Vue 中也有类似的机制, 但官方文档中表示这个特性本意在于给高阶插件 / 组件库提供用例. 并不推荐直接用于应用程序代码中., 不过我们还是可以简单了解下它的使用方法:
- // 祖先级组件
- export default {
- name: 'App',
- provide: {
- theme: 'meicai'
- }
- }
- // 子组件, 获取 provide 中定义的数据
- export default {
- inject: ['theme']
- }
React 高阶组件
高阶组件是 React 中的一个概念, 简称 HOC(Higher Order Component), 定义是: 高阶组件就是一个函数, 且该函数接受一个组件作为参数, 并返回一个新的组件. 例如下面这个例子:
- const withHeader = (WrappedComponent) =>
- class WrapperComponent extends Component {
- render() {
- return <section>
- <header><h1 > 顶部信息</h1></header>
- <WrappedComponent {...this.props} />
- </section>
- }
- }
其中参数 WrappedComponent 为 React 组件, 我们可以在其他组件中使用装饰器的语法来使用这个高阶组件:
- import React, { Component } from 'react';
- import withHeader from './withHeader'
- @withHeader // 装饰器写法
- class Section extends Component {
- state = {
- content: '我是 SectionOne'
- };
- render() {
- return (
- <div>
- { this.state.content }
- </div>
- );
- }
- }
这样, 这个 Section 组件最后 render 的效果即包含了 withHeader 中添加的顶部信息等元素, 也包括了自己本身的 DOM 结构.
除了从结构上对子组件进行增强外, 高阶组件也可以通过注入 props 的方式增强子组件的功能, 在 ant-design 的 Form 组件中, 就有类似的用法, 以下我们举个简化的例子, 实现给受控组件绑定值并且设定 onChange:
- import React, { Component } from 'react';
- // 表单高阶组件
- const Form = (Wrapped) =>
- class WrapperComponent extends Component {
- state = {
- fields: {}
- };
- getFieldValue = (name) => {
- return this.state.fields[name];
- };
- setFieldValue = (name, value) => {
- const fields = this.state.fields;
- fields[name] = value;
- this.setState({ fields });
- };
- getFieldDecorator = (name, value) => {
- const { fields } = this.state;
- if (fields[name] === undefined) {
- fields[name] = value || '';
- this.setState({ fields })
- }
- return (WrappedInput) =>
- React.cloneElement(WrappedInput,
- Object.assign({}, WrappedInput.props, { value: fields[name], onChange: (e) => this.setFieldValue(name, e.target.value) }));
- };
- render () {
- const { getFieldValue, getFieldDecorator } = this;
- // 注入新的 props 对象, 将高阶组件的方法传入到子组件中
- const form = {
- state: this.state,
- getFieldValue,
- getFieldDecorator,
- };
- return (<Wrapped {...this.props} form={form}></Wrapped>);
- }
- };
- export default Form
- // 使用方式
- import React, { Component } from 'react';
- import Form from './form'
- class Demo extends Component {
- checkValue = (e) => {
- const { getFieldValue } = this.props.form;
- console.log(getFieldValue('title'));
- }
- render() {
- const { getFieldDecorator } = this.props.form;
- return (
- <div>
- { getFieldDecorator('title')(<input />) }
- <button onClick={this.checkValue}>获取输入框值</button>
- </div>
- );
- }
- }
- export default Form(Demo)
##6. 状态管理 状态管理是在处理大型项目中组件通信常用的设计模式, Vue 和 React 都采取了这种模式并拥有自己的实现方式. 两者在大概念上没有什么的不同, 从两者的流程图上也能看出来:
Vue 状态管理流程图:
React 状态管理流程图:
两张图都标明了整个过程为一个单项数据流, 从状态映射视图, 视图 (组件) 操作动作, 动作影响状态.
所以这节就简单对比下两者在使用的区别:
Vue:
定义状态及动作
使用状态并含有动作的组件
React:
定义状态及动作
使用状态并含有动作的组件
7. 小结
最后我们回顾下, 本文主要从以下这几个方面对比了 Vue 和 React 的不同:
处理渲染, 事件和表单的不同
常见属性的不同
组件及组件间关系的不同
状态管理使用的不同
希望从这几个方面切入可以帮大家快速的了解另一门框架, 本文所包含案例
来源: https://juejin.im/post/5c2de832f265da6172659b45