前情提要
我学习和使用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进行操作
组件形式
Vue Template
- Vue组件的编写结构分为三部分,分别是 template(HTML) script(JS) style(CSS)
- 将三者独立开来,更好的注重每一部分的开发
- CSS:Vue默认是在单文件组件中在<script scoped></script>标签里写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来构建页面,可以在里面写逻辑,我觉得非常安逸呀。
- 但其实不管是哪一种框架,适合这个项目的框架那它就是好框架,不同的框架有不同的特殊点,如果这个特殊点和项目的要求想契合的话,那就是它了!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。