likes
comments
collection
share

举起Vue2大刀转战React16(二)

作者站长头像
站长
· 阅读数 50

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

大家好,我是猿小蚁(简称"小蚁"),因为我个人在整个我们这个社会生态中是个很微小的存在,我难以想象一个"离群索居"的人会是什么样子的,就像是蚂蚁和蚁群的关系一样,但蚂蚁个体的力量却是很强大的,它能举起超越自己体中几十倍以上的重物,而我却无法在前端领域中表现的那么强悍的力量,所以我想成为一只小蚂蚁,在强大的合作/协作关系网中变得very strong.

上一个阶段完成通过vue的渲染学习react的渲染,通过react和vue分别完成了一个todoList的功能,产生了如下的认识:

  • vue从项目入手、搭建、开发都比较简单,只要记住响应的格式即可;
  • 而react项目搭建非常复杂,必须webpack打包工具和babel转义;在使用表单元素也比较头疼

本阶段从基础语法进入组件的对比学习,从组件注册、组件传值、插槽、组件状态和生命周期等角度入手,里面的内容更加精彩哟!!

1. 组件认识

无论是vue还是react,其中最重要的一个特性是组件系统,每一个组件都可以封装一个UI页面及功能。 通过vue的组件概念来认识react组件,1. 可以总结对vue组件的认识;2. 可以以熟悉的线索探索未知的领域,更好的理解新事物和对比新事物。

2. 组件注册

2.1 Vue实现组件注册

  1. 全局注册组件: Vue.use() 或者 Vue.component()
import Vue from "vue";
​
Vue.component("Demo", {
    data(){},
    template: "<div>Demo component</div>",
})
  1. 局部注册组件: 使用 components 选项
import Vue from "vue";
​
new Vue({
    components: {
        Demo: {
            data(){},
            template: "<div>Demo component</div>",
        }
    }
})

2.2 React实现组件注册

找寻了官网以及各种资料, 里面没有定义全局组件和局部组件的概念,大概是坚定的执行了模块化系统的原因吧!!但是React对组件的定义方式有两种:类组件函数组件。

  1. 函数组件: 需要有返回值
const Demo = (props) => {
    return <div>Demo component</div>
}
  1. 类组件:需要有render函数
class Demo extends React.Component{
    constructor(){
        super(props);
        this.state = {};
    }
    
    render(){
        return <div>Demo component</div>;
    }
}
  1. 注意事项:

    1. 组件名称首字母必须大写, 用来区分组件和普通标签
    2. jsx语法外层必须且只能有一个根元素

3. 组件间传值

3.1 Vue实现组件间传值

  • 通过在子组件中的 props 选项中定义 父组件给子组件传的值、值的类型、值的默认值以及值的验证
  • 在父组件中使用 :propName="propValue"
<!-- 父组件 -->
<template>
    <ChildComponent :msg="msg" @click="doClick"></ChildComponent>
</template>
<script>
import ChildComponent from "./ChildComponent.vue";
export default {
    components: {ChildComponent},
    data(){
        return {
            msg:"",
        }
    },
    methods: {
        doClick(){}
    }
}
</script>
<!-- 子组件ChildComponent -->
<template>
    <div>
        <span>{{msg}}</span>
        <button @click="$emit('doClick')">点我</button>
    </div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: "hello, my child"
        }
    }
}
</script>

3.2 React实现组件间传值

由于react的组件实现方式有两种,即函数组件和类组件,两者的传值方式不一样

  • 父组件:父组件的实现和vue中的实现方式类似
let Parent = () => {
    const doClick = () => { console.log("点击了")}
    return <Child msg={msg} doClick={doClick}></Child>
}
  • 函数组件
let Child = (props) => {
    return (
        <div>
            <span>{props.msg}</span>
            <button onClick={props.doClick}>点我</button>
        </div>
    )
}
// 设置默认值
Child.defaultProps = {
    msg: "hello, my Child",
}
  • 类组件
class Child extends Component {
    static defaultProps = {
        msg: "hello, my Child"
    }
​
    render(){
        return (
            <div>
                <span>{this.props.msg}</span>
                <button onClick={this.props.doClick}>点我</button>
            </div>
        )
    }
}

3.3 总结

对于组件间传值,vue可以实现定义数据类型、定义默认值、定义验证逻辑;

从这三个角度出发分析react,发现react的组件间传值可以实现定义默认值,但定义数据类型和验证逻辑应该需要其他的方法,比如ts的数据类型和检查规则,或者prop-types

对于设置默认值来说,采取 定义在类静态属性或函数对象上的方式,有点不可思议哟,这是重大区别!!!

组件间通信差别:

  • vue:

    • 通过props完成父组件向子组件的传值
    • 通过$emit完成子组件向父组件的传值
  • react

    • 通过props完成父组件向子组件的传值
    • 通过props完成父组件向子组件的传递父组件的函数,通过调用函数传递数据

4. 跨组件传值

4.1 Vue实现跨组件传值

  • 通过Provide/inject实现跨组件传值,在父组件中使用Provide提供值;在子组件或子子组件中使用inject获取值
  • 通过EventBus来实现跨组件传值,但存在多种问题(不推荐)
<!-- 父组件 -->
<template>
    <ChildComponent></ChildComponent>
</template>
<script>
import ChildComponent from "./ChildComponent.vue";
export default {
    components: {ChildComponent},
    provide(){
        return {
            doClick: () => {
                console.log(this.msg);  // 父组件本身的值
            },
            msg: "父组件给子组件的值"
        }
    },
    data(){
        return {
            msg:"父组件本身的值",  // 不会冲突
        }
    },
}
</script>
<!-- 子组件ChildComponent -->
<template>
    <div>
        <span>{{msg}}</span>
        <button @click="doClick">点我</button>
    </div>
</template>
<script>
export default {
    inject: ["msg", "doClick"]
}
</script>

通过手动实践,发现provide/inject还有有点东西, 它是独立于组件其他选项的数据作用域,所以一个组件中可以存在相同的变量名provide和其他选项中,而不会发生冲突!

4.2 React实现跨组件传值

在vue官网中有如下介绍:

Provide/inject——如果你熟悉 React,这与 React 的上下文特性很相似。

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语 言。

// userContext.js
import React from "react";
​
const userContext = React.createContext("default value")
const UserProvider = userContext.Provider
const UserConsumer = userContext.Consumerexport { UserProvider, UserConsumer }
export default userContext; // 写法二
// app.js
import React from "react";
import ReactDOM from "react-dom";
import { UserProvider } from "./userContext"
class App extends React.Component {
  render() {
    return (
      <UserProvider value="Hello React Context">
        <A />
      </UserProvider>
    )
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
// 使用类组件时,render时第一个参数是组件,而不是类名
// A.js
// 写法一
import React from "react";
import { UserConsumer } from "./userContext"export class A extends React.Component {
  render() {
    return (
      <div>
        <UserConsumer>
          {username => {
            return <div>{username}</div>
          }}
        </UserConsumer>
      </div>
    )
  }
}
import React from "react";
​
import userContext from "./userContext";
​
export class C extends React.Component{
    static contextType = userContext;
    render(){
        return (
            <div>{this.context}</div>
        )
    }
    
}

4.3 总结

  • Vue中的跨组件传值,通过provide和inject选项;React中跨组件传值,通过Provider和Consumer组件(表面上看是组件的用法)来实现
  • vue中使用provide/inject选项只要在option对象中声明对应的选项即可;而react需要先创建context才能使用Provider和Consumer。

5. 组件插槽

组件插槽存在的意义:

  • 组件的调用和原生标签的使用方法差不多,但原生标签可以标签嵌套标签,完成dom的构建
  • 那自定义组件为了完成与原生标签差不多的功能——嵌套标签,实现了一套内容分发的API,这就是组件插槽
  • 组件插槽可以任意的组合组件,提高复用性,增加组件的灵活性

5.1 Vue实现组件插槽

通过v-slot指令和slot标签名实现

Vue中的插槽系统是一个非常复杂的系统,包括如下:

  • 匿名插槽(单个插槽/默认插槽/ default)
  • 具名插槽(多个插槽/name设置名称)
  • 作用域插槽(用于传递数据)
// 定义插槽组件
Vue.component("SlotComp", {
    template: `
        <div class="block">
            <div class="block-title">
                <slot>默认标题</slot>
            </div>
            <div class="block-content">
                <slot name="blockcontent" :msg="msgData">默认内容</slot>
            </div>
            <div class="block-footer">
                <slot name="blockfooter"></slot>  
            </div>
        </div>
    `,
    props: {
        msg: [String],
    },
    computed: {
        msgData() {
            return "插槽数据为:" + this.msg + "插槽子组件";
        },
    },
});
// 定义辅助组件
Vue.component("ChildComp", {
    template: `
        <h2>{{msg}}</h2>
    `,
    props: {
        msg: [String],
    },
});
// 使用插槽组件
let vm = new Vue({
    el: "#app",
    template: `
        <div>
            <SlotComp :msg="msg">
                <template v-slot:default>
                    <span>我的标题</span>  
                </template>
                <template v-slot:blockcontent="{msg}">
                    <ChildComp :msg="msg"></ChildComp>
                </template>
                <template v-slot:blockfooter>
                    <button @click="confirm">确认</button>  
                </template>
            </SlotComp>
            <p>当前组件的msg的值为:{{msg}}</p>
        </div>
    `,
    data() {
        return {
            msg: "父组件的",
        };
    },
    methods: {
        confirm() {
            console.log("你进行了确认操作");
        },
    },
});

5.2 React实现组件插槽

React在定义中是没有插槽的概念的!!!但并不以为这不能用组件的嵌套结合,实际上会更简单。 React中插槽需要转变认知角度:React中组件实际上是函数,函数是可以传递对象/函数/普通数据等,同时可以返回对象/函数/普通数据。 组件标签之间的内容会被记录为props.children,因此只用获取children的值并进行渲染即可。

// 父组件
const Parent = () => {
  return (
    <div>
      <div>我是父组件</div>
      <Child>
                <div>我是父组件传给子组件的组件</div>
        <Child></Child>
      </Child>
    </div>
  );
};
​
// 子组件
const Child = (props) => {
  console.log(props);
  return (
    <div>
      <div>我是子组件-header</div>
      <div>{props.children}</div>
      <div>我是子组件-footer</div>
    </div>
  );
};

5.3 总结

  • Vue中的插槽使用比较复杂, 分成多种情况:单个插槽、多个插槽、默认插槽、具名插槽、作用域插槽,使用相对比较复杂;
  • React比较简单粗暴,直接将所有的数据通过props传给子组件,子组件就可以相机渲染内容了,这一点我更喜欢React的模式,更加灵活多变,操作性更强; 在官网称这种方式是组合, 当然还有继承,不过react官方团队都没遇到过这种情况,组合的方式已经可以解决问题了。

6. 组件状态

组件实际上就是一个黑盒子,对于组件来说:既有输入(props),又有输出(事件),当然也会有自己的状态,这个状态在Vue就是data/computed这两个选项对应的值。 而对于项目级的状态管理方案, Vue主要采用vuex实现;而react的方案就很多了, 常用的有:Redux、Mobx、Dva、Reduxjs/toolkit(关于这方面会在详细使用时再进行分析)。

6.1 Vue实现组件状态

vue组件状态由于响应式原理的存在,在修改时会非常方便

  • 定义组件方面:就在data和computed选项中
  • 修改data值很方面,直接this.xxx修改
  • 双向绑定简直无敌,一个v-model指令就实现了
Vue.component("ChildComp", {
    template: `
        <div>
            <input type="text" v-model="name"></input>
            <button @click="changePerson">变身猿小蚁</button>  
            <h2>{{msg}}</h2>
        </div>
    `,
    computed: {
        msg() {     // 定义计算属性状态
            return "hello, " + this.name;
        },
    },
    data() {
        return {
            name: "猿小蚁",  // 定义组件状态,同时通过v-model实现了双向绑定
        };
    },
    methods: {
        changePerson() {
            this.name = "猿小蚁";  // 修改组件状态
        },
    },
});
​
let vm = new Vue({
    el: "#app",
    template: `
        <ChildComp :msg="msg"></ChildComp>
    `,
});

6.2 React实现组件状态

React组件的状态其实指的是 类组件状态,对于函数组件没有任何意义

class ChildComp extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "猿小蚁",   // 定义类组件状态
      msg: "hello, 猿小蚁",  
    };
    this.changePerson = this.changePerson.bind(this);
    this.changeName = this.changeName.bind(this);
  }
  changePerson() {
    this.setState({  // 修改类组件状态-
      name: "猿小蚁",  
      msg: "hello, 猿小蚁",  // 同步修改msg状态,想完成计算属性的效果,但实际上没有
            // 当然可以通过代理或者defineProperty实现
    });
  }
  changeName(e) { // 响应事件,修改类组件状态,实际上完成了双向绑定,类似于v-bind
    this.setState({
      name: e.target.value,
      msg: "hello, " + e.target.value,
    });
  }
  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.name}
          onChange={this.changeName}
        ></input>
        <button onClick={this.changePerson}>变身猿小蚁</button>
        <h2>{this.state.msg}</h2>
      </div>
    );
  }
}
ReactDOM.render(<ChildComp />, document.getElementById("root"));

6.3 总结

双向绑定:

  • 组件类中更新了状态,DOM 状态同步更新,
  • DOM 更改了状态,组件类中同步更新。
  • 组件 <=> 视图。
  • 对于Vue来说,组件状态通过data定义,同时computed定义的计算属性状态可以自行发生变化;
  • 而React类组件来说,组件状态定义在state对象属性上,但没有计算属性的概念;
  • Vue中存在双向绑定;而React没有封装,需要通过value和事件来一起作用;

7. 组件生命周期

组件生命周期:生命周期一般代表着就是从产生到消亡的过程,在这整个过程中会仍然有不同的阶段,在组件中就包括初始化、更新和卸载等过程,通过分析Vue的过程同时对比分析react的过程如下:

VueReact
初始化创建前: beforeCreate() 创建后:created() 挂载前:beforeMount() 挂载后:mounted()constructor() static getDerivedStateFromProps() render() componentDidMount()
更新更新前:beforeUpdate() 更新后:updated()static getDerivedStateFromProps() shouldComponentUpdate() render() getSnapshotBeforeUpdate() componentDidUpdate()
卸载卸载前:beforeDestroy() 卸载后:destroyed()componentWillUnmount()
错误处理errorCapturedstatic getDerivedStateFromError() componentDidCatch()
其他激活:activated() 失活:deactivated()****

注意: getDerivedStateFromProps这个函数在React16.3和React16.4+之间是有差别的,具体主要是16.4+中setState()forceUpdate()同样会触发这个方法;而React16.3并不会触发这个方法。

举起Vue2大刀转战React16(二)

举起Vue2大刀转战React16(二)

8. 总结

对比项Vue2.xReact16.x
作用作用:封装一部分的UI功能作用:封装一部分的UI功能
组件注册全局注册:Vue.component() 局部注册:components: {}类组件 函数组件
组件间传值props 类型: type 默认值: default 校验: validatorprops 类组件: this.props.xxx 函数组件: (props)=> {} 默认值:定义到类/函数的属性defaultProps
跨组件传值provide/injectcontext
插槽, v-slotprops.children
组件状态data(){return {.....}} computed:{} 双向绑定:v-model自动实现定义状态:this.state={....} 改变状态:this.setState({}) 双向绑定:手动实现
生命周期一共11个:创建2个,挂载2个,更新2个, 销毁2个,激活/失活2个,问题捕捉1个一共12个:挂载4个,更新5个,卸载1个,错误处理2个

至此vue和react的基础知识都基本上完成后了, 后续将进行项目实践源码分析,相信通过对vue源码的学习,react的源码将不在话下,期待大家继续关注,有什么想法、建议和思考,欢迎大家在留言区驻留,期待你们的出现,和小蚁一起成长!!!!

转载自:https://juejin.cn/post/7140257162713890824
评论
请登录