举起Vue2大刀转战React16(二)
“我报名参加金石计划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实现组件注册
- 全局注册组件: Vue.use() 或者 Vue.component()
import Vue from "vue";
Vue.component("Demo", {
data(){},
template: "<div>Demo component</div>",
})
- 局部注册组件: 使用
components
选项
import Vue from "vue";
new Vue({
components: {
Demo: {
data(){},
template: "<div>Demo component</div>",
}
}
})
2.2 React实现组件注册
找寻了官网以及各种资料, 里面没有定义全局组件和局部组件的概念,大概是坚定的执行了模块化系统的原因吧!!但是React对组件的定义方式有两种:类组件和函数组件。
- 函数组件: 需要有返回值
const Demo = (props) => {
return <div>Demo component</div>
}
- 类组件:需要有render函数
class Demo extends React.Component{
constructor(){
super(props);
this.state = {};
}
render(){
return <div>Demo component</div>;
}
}
-
注意事项:
- 组件名称首字母必须大写, 用来区分组件和普通标签
- 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.Consumer
export { 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的过程如下:
Vue | React | |
---|---|---|
初始化 | 创建前: beforeCreate() 创建后:created() 挂载前:beforeMount() 挂载后:mounted() | constructor() static getDerivedStateFromProps() render() componentDidMount() |
更新 | 更新前:beforeUpdate() 更新后:updated() | static getDerivedStateFromProps() shouldComponentUpdate() render() getSnapshotBeforeUpdate() componentDidUpdate() |
卸载 | 卸载前:beforeDestroy() 卸载后:destroyed() | componentWillUnmount() |
错误处理 | errorCaptured | static getDerivedStateFromError() componentDidCatch() |
其他 | 激活:activated() 失活:deactivated()**** | 无 |
注意: getDerivedStateFromProps这个函数在React16.3和React16.4+之间是有差别的,具体主要是16.4+中setState()
和forceUpdate()
同样会触发这个方法;而React16.3并不会触发这个方法。
8. 总结
对比项 | Vue2.x | React16.x |
---|---|---|
作用 | 作用:封装一部分的UI功能 | 作用:封装一部分的UI功能 |
组件注册 | 全局注册:Vue.component() 局部注册:components: {} | 类组件 函数组件 |
组件间传值 | props 类型: type 默认值: default 校验: validator | props 类组件: this.props.xxx 函数组件: (props)=> {} 默认值:定义到类/函数的属性defaultProps |
跨组件传值 | provide/inject | context |
插槽 | , v-slot | props.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