前情提要
我学习和使用Vue大概有一年时间了,但是最近才开始学习的React,在没使用过React之前就经常看到许多博主都在将两者进行对比,在实践完以后,我想站在我自己的角度看看。
那就开始了
官方文档
- 个人感觉Vue的文档划分的更加好一点,会把许多概念都讲的比较清晰(虽然第一次看的时候啥都没看懂)同时把一些相关的生态系统也归纳到了一起,例如vue-cli、vue-router、vuex等,比较方便
- React的文档感觉比较注重实操,对基础的讲解较少,在教程部分就会教你如何实现一个井字棋的(这一点是比较友好的)
组件
这两个框架都是推荐把页面组件化的,但是两者之间对于组件也有各自的不同
生命周期
Vue
- 主要分为8个阶段:
- beforeCreate
- created (在这个阶段就可以首次拿到data中定义的数据)
- beforeMount
- mounted(在这个阶段Dom树渲染完毕可访问Dom结构)
- beforeUpdate
- updated
- beforeDestroy
- destroyed
React
- 主要分为3个阶段:
Mounting初始化阶段 -> Updating运行中阶段 -> Unmounting销毁阶段
- 生命周期中的方法
- 【react 16.0之后已移除】
componentWillMount():在render()之前执行,所以在这个阶段的setState()不会重新渲染;通常用constructor()代替 render():在componentWillMount()之后执行componentDidMount():在render()之后执行,所以在这个阶段的setState()会重新渲染;这里可以对DOM进行操作- 【react 16.0之后已移除】
componentWillReceiveProps(nextProps):当已挂载组件收到新的props时执行,但有可能props传入时没有发生改变但仍然重新渲染了,如果想要避免这种情况可以在传入新的props时比较一下新旧props之间是否发生变化;这里需要注意一下,如果只是调用了setState()的话是不会触发这个方法的,只有props发生改变或者没有发生改变时才会触发这个方法 shouldComponentUpdate(nextProps,nextState):在接收到新的props或者state时确定是否发生重新渲染,默认值为true;在首次渲染时不会触发这个方法- 【react 16.0之后已移除】
componentWillUpdate(nextProps,nextState):在shouldComponentUpdate()之后,render()之前触发;在组件初始化时不会被触发 componentDidUpdate(prevProps,prevState):在componentWillUpdate(nextProps,nextState)之后调用;在组件初始化时不会被触发,此时可以对DOM进行操作
- 【react 16.0之后已移除】
组件形式
Vue Template
- Vue组件的编写结构分为三部分,分别是
template(HTML) script(JS) style(CSS) - 将三者独立开来,更好的注重每一部分的开发
- CSS:Vue默认是在单文件组件中在标签里写CSS样式,我们可以通过在
<script scoped></script>标签中写上scoped,这样这部分样式就只用来控制该组件的样式,而不会影响到其他组件。 - JS:模版部分使用数据:需要将变量放在双大括号内
{{}}
<template>
<!-- HTML Code -->
<div>{{name}}</div>
</template>
<script>
export default{
name: 'NewComponent',
data() {
return {
name: 'xx'
}
}
}
</script>
<style scoped>
/* CSS Code */
</style>
React JSX
- React支持Class组件和Function组件
- React组件使用render渲染函数,可以用JavaScript来构建视图页面,常包含了大量的逻辑
- CSS:React是通过使用className字段来设置样式,一般都是引入外部的css,且一般都会使用less预编译
- JS:将变量放在
{}内
// class组件
class Template extends React.Component{
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
</div>
);
}
}
// function组件
function NewComponent() {
const [name, setName] = useState('');
return (<div>{name}</div>);
}
数据管理
Vue对象属性
- 数据由data属性在对象中进行管理,可以直接改变
export default {
name: 'app',
data() {
return {
orders: {}
}
}
}
React状态管理
class组件state
- 数据通过state保存状态,需要通过setState来改变
- setState更新的是组件的部分数据,react会自动将数据合并
- setState原理:你真的理解setState吗
state = {
version: Version.V3
};
handleVersionChange(version: Version) {
this.setState({ version: Number(version)}
);
function组件hooks
- 数据需要通过setXXX来改变
- useState 不会自动合并更新对象
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
<button onClick={() => setCount(initialCount)}>Reset</button>
</>
);
}
数据流
单向数据流中的单向:我理解的是父级数据的更新会向下流动到子组件中,但是反过来则不行,这样可以防止从子组件意外改变父级组件的状态
Vue双向数据绑定
vue仍然是单向数据流
表现形式
- 使用
v-model - 一般有两种使用情景:
- 组件
input元素
v-model实质为语法糖
<input v-model="message" />
// 等价于:
<input
v-bind:value="message" // 与data中声明的数据进行绑定
v-on:input="message=$event.target.value" // 监听输入框中值的变化
/>
// 实现原理
// 修改AST元素,给el添加一个prop,相当于动态绑定了value
addProp(el,'value',`(${value})`);
// 添加事件处理,相当于给input绑定了input事件
addHandler(el,event,code,null,true);
实现原理
- vue使用的是发布者订阅者的模式+数据劫持的方式来实现的,在组件中我们定义data时,vue会遍历这个对象,通过
Object.defineProperty()给每一个值都添加getter和setter方法,然后每一个组件都有一个watcher来监听数据的变化,当数据发生变化的时候,即触发setter时,就会发布消息给订阅者,触发相应的监听回调
Vue2.0的缺陷
- 不具备监听数组的能力,需要重新定义数组的原型来达到响应式
- 无法检测到对象属性的添加和删除
- 只能劫持对象的属性(假如需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择) 解决方法:Vue.set() || this.$set()
- 如果在实例创建之后添加新的属性到实例上,它不会触发视图更新,因为此时添加的属性并没有绑定上
getter、setter,因此这个属性是非响应式。如果我们想要把这个属性变成响应式的话,可以通过Vue.set()或者this.$set()这两种方式添加新的属性 - 除了添加新的属性以外,改变数组的一些属性也不会使得视图更新,
- 利用索引直接设置数组的某一项
arr[1] = 10 - 直接修改数组的大小
arr.length = 10 - 对于数组除了可以通过
Vue.set()、this.$set()这两种方式,还可以使用数组的一些API来触发更新,如splice()、concat()等会修改原始数组的API
Vue3.0的改进
- 使用ES6中的proxy
- 可以劫持到整个对象而非属性,并返回一个新的对象
- 可以监听到数组的变化
React单向数据流
特点
- React采用单向数据流的形式,即它只接收数据,但不改变数据,只监听数据的改变;当数据发生变化的时候,它会接收使用新的数据,重新进行渲染。
- 我觉得单向数据流最大的好处的话,是当多个组件都要对同一个值进行操作的时候,由于数据只有一份,因此能够保持它的一致性。
状态管理器
什么情况下我们要使用状态管理器呢?
一般是当多个组件需要共同维护一些数据,使用传值的方式会十分复杂,因而我们可以把这些需要共同维护的数据提取出来,放在store中共同维护,这样就可以保证数据的一致性
Vue
Vuex
- 单一数据源:只有一个store
- mutation(同步操作):修改state只能通过mutation的方式,需要通过store.commit()来提交mutation
- action(异步操作):action里面可以包含多个mutation,最后也是要通过store.commit()来触发mutation,action需要通过store.dispatch()来触发
- model:引入来模块化,就是想要把store进行拆分,每个 Module 有自己的 state、mutation、action、getter,可以各自维护,最后再把每个store组合起来,不违背Vuex的单一数据源
React
Redux
- 单一数据源:只有一个store
- state是只读的:需要通过reducer纯函数返回一个新的state
- 单向数据流
- 使用的是不可变数据,每次更新都是使用新的state来替换旧的state
Mobx-React
- 可以有多个store
- store中的state可以直接修改
@observable // 我们要在需要观察的数据加上@observable
current: number = 1;
total: number = 0;
@action // 将修改被观测变量的行为放在action中
updateCurrent(current: number) {
this.current = current;
}
// 对于异步操作,action无法影响当前函数调用的异步操作,可以使用runInAction来解决
async getList() {
// some code
runInAction(() => {
this.total = res.total;
});
}
路由
Vue-Router
vue是单页面应用,页面跳转不会发送请求
hash模式
- 简单点说就是URL后带有#标志的就是hash路由
// 像这样
http://localhost:8080/home#heading
表层原理
- 使用hash来模拟一个完整的URL,所以当URL发生改变的时候,不会重新加载
- hash出现在URL中,但并不会包含到http请求中,因此当hash值发生变化的时候,并不会引起页面的请求
- 当#后的值发生变化的时候,会向浏览器的历史浏览增加一个记录,当点击后退按钮时,会弹出,以此来实现页面的跳转
- 通过onhashchange事件监听hash值的变化
history模式
- URL后不带有#标志的
// 像这样
http://localhost:8080/home/center
表层原理
- history模式主要使用的是HTML5中提供的API
history.pushState()和history.replaceState()来实现 - 需要注意的是,history模式不怕前进,不怕后退,就怕刷新
- history模式下如果刷新页面的话,可能会返回404,因为当刷新页面的时候,会真实的向服务端发送请求,但如果此时该页面没有在服务端配置过的话就会返回404,因此需要服务端进行重定向,可以使用Nginx,使用try_file和rewrite实现重定向
React-Router(待完善)
BrowserRouter
- 与Vue-Router中的history模式类似
HashRouter
- 与Vue-Router中的hash模式类似
脚手架(待补充)
Vue-Cli
Create-React-App
单元测试(待补充)
从Vue到React
- 虽然我是先学习和使用Vue的,但是由于以前学习过Java等一些面向对象的语言,所以从Vue到React的过渡并没有不适应,反而觉得很熟悉;而且React使用JS来构建页面,可以在里面写逻辑,我觉得非常安逸呀。
- 但其实不管是哪一种框架,适合这个项目的框架那它就是好框架,不同的框架有不同的特殊点,如果这个特殊点和项目的要求想契合的话,那就是它了!