Vue和React都是目前最流行、生态最好的前端框架之一。框架自己没有优劣之分,只有适用之别,选择符合自身业务场景、团队基础的技术才是咱们最主要的目的。css
博主1年前用的是Vue框架,近半年转技术栈到React框架,对于Vue和React都有一些基本的了解。接下来博主将与你们一块儿走近Vue和React,共同探讨它们的差别。(好比你正在用vue,对react感兴趣也能够看下二者之间的用法差别,反之亦然)html
总体内容概览:前端
因为内容较长,将分上、中、下三部分和你们一块儿共同探讨(有不一样意见的欢迎文章下方一块儿探讨交流,感谢支持)。vue
Google 前工程师尤雨溪于 2014 年建立了这个框架。Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不一样的是,Vue 被设计为能够自底向上逐层应用。Vue 的核心库只关注视图层,不只易于上手,还便于与第三方库或既有项目整合。java
与 Vue 不一样,react 库是由 Facebook 建立的。最初是为了 Facebook 广告流量管理建立的。那时 Facebook 遇到了维护和编码方面的问题。它以动态建立和交互式 UI 的能力而闻名。react
vue与react都推崇组件式的开发理念,可是在设计的核心思想上有很大差异。es6
vue的总体思想仍然是拥抱经典的html(结构)+css(表现)+js(行为)的形式,vue鼓励开发者使用template模板,并提供指令供开发者使用(v-if、v-show、v-for等等),所以在开发vue应用的时候会有一种在写经典web应用(结构、表现、行为分离)的感受。另外一方面,在针对组件数据上,vue2.0经过Object.defineProperty
对数据作到了更细致的监听,精准实现组件级别的更新。web
react总体上是函数式的思想,组件使用jsx语法,all in js,将html与css全都融入javaScript,jsx语法相对来讲更加灵活,我一开始刚转过来也不是很适应,感受写react应用感受就像是在写javaScript。当组件调用setState或props变化的时候,组件内部render会从新渲染,子组件也会随之从新渲染,能够经过shouldComponentUpdate
或者PureComponent
能够避免没必要要的从新渲染(我的感受这一点上不如vue作的好)。api
vue组件定义使用xx.vue文件来表示,vue组件将html、css、js组合到一块儿,模板部分使用数据使用{{}}
,形式以下:数组
// 模板(html) <template> <div>{{name}}</div> </template> // 数据管理(js) <script> export default { name: 'NewComponent', data() { return { name: 'xx' } } } </script> // 样式(css) <style scoped> </style> 复制代码
组件使用:
<new-component name="xx" /> 复制代码
react推荐使用jsx或者js文件来表示组件,react支持class组件和function组件2种形式,react使用{}
包裹变量,这点须要注意。
注意: 组件名称必须以大写字母开头。React 会将以小写字母开头的组件视为原生 DOM 标签。例如,
<div />
表明 HTML 的 div 标签,而<Welcome />
则表明一个组件,而且需在做用域内使用 Welcome。
(1)class组件
import React from 'react'; class NewComponent extends React.Component { constructor(props) { super(props); this.state = { name: 'xx' }; } render() { rerurn(<div>{name}</div>); } } export default NewComponent; 复制代码
(2)function组件
hooks的出现赋予了function组件管理state的能力。
import React, { useState } from 'react'; function NewComponent() { const [name, setName] = useState(''); return (<div>{name}</div>); } export default NewComponent; 复制代码
组件数据管理一般包含2部分,来自父组件的数据props与自身的数据。
vue与react中的props都是单向数据流的,父级prop的更新会向下流动到子组件中,可是反过来则不行。prop能够是数组或对象,用于接收来自父组件的数据。
vue中的props支持传递静态或动态props,静态props通常传递字符串。
<blog-post title="My journey with Vue"></blog-post> 复制代码
静态prop传递布尔值true能够这样写,传值false仍然须要使用动态prop传值。
<blog-post disabled></blog-post>
复制代码
动态赋值使用v-bind,能够简写为:。
<blog-post v-bind:title="tile"></blog-post> // 简写形式 <blog-post :title="tile"></blog-post> 复制代码
动态prop经常使用来传递对象、数组、布尔值(false值,true值能够直接传属性)等。
<blog-post :title="post.title + ' by ' + post.author.name"></blog-post> 复制代码
vue中使用data来管理组件的数据,vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性可以响应数据变化。对象必须是纯粹的对象 (含有零个或多个的 key/value 对)。一旦观察过,不须要再次在数据对象上添加响应式属性。所以推荐在建立实例以前,就声明全部的根级响应式属性。
当一个组件被定义,data必须声明为返回一个初始数据对象的函数。
export default { name: 'NewComponent', data() { return { name: 'xxx', age: 12 } } } 复制代码
当须要在组件内部修改数据时,能够直接经过vue实例修改:
methods: { changeName() { this.name = 'new Name'; } } 复制代码
react中的props也与vue同样能够传递静态或动态props,静态props通常传递字符串。
函数组件和class组件均可以使用props,函数组件使用props参数获取父组件传下来的props。
函数组件获取props:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; 复制代码
class组件使用this.props
获取组件props
:
class Welcome extends React.Component { constructor(props) { super(props); } render() { const { name } = this.props; return <div>{name}</div>; } } 复制代码
动态props:
<Welcome name={name} />
复制代码
react中使用state来管理组件内的数据,hooks的出现使得函数组件也具有管理state的能力。
class组件在构造函数(constructor)中定义组件内数据(state),修改数据必须经过setState修改,不能直接修改state,这点很是重要。
class组件使用state:
class Welcome extends React.Component { constructor(props) { super(props); this.state = { name: 'xx' }; this.changeName = this.changeName.bind(this); } changeName() { this.setState({ name: 'new name' }); } render() { const { name } = this.state; return <div onClick={this.changeName}>{name}</div>; } } 复制代码
关于class组建的setState有如下两点说明:
当须要使用上一个state值时,可让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 做为第一个参数,将这次更新被应用时的 props 作为第二个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
复制代码
react 16.0以前函数组件只是纯的渲染组件,hooks的出现赋予了函数组件管理state的能力。
useState返回一个state,以及更新state的函数。若是新的 state 须要经过使用先前的 state 计算得出,那么能够将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。
import React, { useState } from 'react'; function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> </> ); } 复制代码
关于setState有如下三点说明:
- 1.与 class 组件中的 setState 方法不一样,useState 不会自动合并更新对象。
- 2.只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 3.只能在 React 的函数组件或自定义hook中调用 Hook。不要在其余 JavaScript 函数中调用。
组件数据交互是指父子组件、兄弟组件、跨层组件之间传递数据。 兄弟组件之间能够经过事件总线或者经过父组件传递数据,这里就不详细赘述。
对于父子组件数据交互,vue中使用prop+自定义事件实现,react经过props+回调实现。
vue中父组件经过props传递数据给子组件,子组件使用$emit
触发自定义事件,父组件中监听子组件的自定义事件获取子组件传递来的数据。
子组件使用$emit
传递自定义事件myEvent
:
<template> <div @click="changeName">{{name}}</div> </template> <script> export default { name: 'NewComponent', data() { return { name: 'xxx', age: 12 } }, methods: { changeName() { this.name = 'new Name'; this.$emit('myEvent', this.name); } } } </script> 复制代码
父组件使用@myEvent
监听自定义事件,回调函数参数是子组件传回的数据:
<template> <div> <new-component @myEvent="getName"></new-component> </div> </template> <script> import NewComponent from './NewComponent'; export default { components: { NewComponent }, data() { return {} }, methods: { getName(name) { console.log(name) } } } </script> 复制代码
react中父组件使用props传递数据和回调函数给子组件,子组件经过props传下来的回调函数返回数据,父组件经过回调函数获取子组件传递上来的数据。
子组件经过props接收父组件传下来的回调事件:
import React, { useState } from 'react'; function Children(props) { const { myEvent } = props; const [name, setName] = useState('xxx'); const changeName = () => { setName('new name'); myEvent('new name'); }; return <div onClick={changeName}>{name}</div>; } 复制代码
父组件经过回调事件获取子组件传递的参数:
function Parent() { const changeName = name => { console.log(name); }; return <Children myEvent={changeName}></Children>; } 复制代码
vue和react都支持跨组件传递数据,vue中主要经过provide / inject
实现,react中主要经过Context
实现。
vue中经过provide / inject
在祖先组件向全部子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
祖先组件中定义provide选项,provide选项应该是一个对象或返回一个对象的函数。
<template> <div> <new-component @myEvent="getName"></new-component> </div> </template> <script> import NewComponent from './NewComponent'; export default { provide: { // 定义provide选项 message: 'This is a big news' }, components: { NewComponent }, data() { return {} }, methods: { getName(name) { console.log(name) } } } </script> 复制代码
子组件经过inject选项获取祖先组件的provide选项值,inject选项应该是一个字符串数组或者对象。
<template> <div>{{message}}</div> </template> <script> export default { name: 'Children', inject: ['message'], data() { return {} } } </script> 复制代码
注意:provide 和 inject 绑定并非可响应的。这是刻意为之的。然而,若是你传入了一个可监听的对象,那么其对象的属性仍是可响应的。
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
在父组件建立一个Context对象,经过Context.provider
的value属性向消费组件传值。
import React, { useState } from 'react'; // 建立Context对象 const MyContext = React.createContext({ theme: 'black' }); function Parent() { const changeName = name => { console.log(name); }; // Context.provider向消费组件传值 return (<MyContext.Provider value={{ theme: 'white' }}> <Children myEvent={changeName}></Children>; </MyContext.Provider>); } 复制代码
消费组件获取Context有2种方式:
(1)class组件经过contextType获取最近Context上的那个值。
class DeepChildren1 extends React.Component { constructor(props) { super(props); } static contextType = MyContext; render() { return <div>{this.context.theme}123</div>; } } 复制代码
(2)函数式组件经过Context.Consumer
订阅到Context的变动。
function DeepChildren(props) { return (<MyContext.Consumer> { (value) => ( <div>{value.theme}</div> ) } </MyContext.Consumer>); } 复制代码
关于Context须要注意:
当Provider的父组件进行重渲染时,consumers组件会从新渲染,而且没有办法避免,应该尽可能避免使用Context。
关于class与style处理上,vue与react也存在较大差别。
vue对class与style特地作了加强,能够传字符串、对象、数组。
(1)给class绑定字符串:
<div class="hello"></div> 复制代码
(2)给class绑定对象:
<div class="static" :class="{ active: isActive, 'text-danger': hasError }" ></div> 复制代码
data以下:
data: { isActive: true, hasError: false } 复制代码
HTML 将被渲染为:
<div class="static active"></div> 复制代码
(3)给class绑定数组:
<div :class="[activeClass, errorClass]"></div> 复制代码
data以下:
data: { activeClass: 'active', errorClass: 'text-danger' } 复制代码
HTML 将被渲染为:
<div class="active text-danger"></div> 复制代码
(4)class还能够直接绑定到组件上,这一点react并不支持。
声明组件以下:
Vue.component('my-component', { template: '<p class="foo bar">Hi</p>' }) 复制代码
在使用它的时候添加一些 class:
<my-component class="baz boo"></my-component> 复制代码
HTML 将被渲染为:
<p class="foo bar baz boo">Hi</p> 复制代码
style用来绑定内联样式,支持传对象、数组,使用须要添加浏览器引擎前缀的 CSS 属性时,如 transform,Vue.js 会自动侦测并添加相应的前缀。
(1)传对象,CSS 属性名能够用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> 复制代码
data以下:
data: { activeColor: 'red', fontSize: 20 } 复制代码
HTML将被渲染为:
<div style="color: red; font-size: 20px;"></div> 复制代码
(2)传数组将多个样式应用到同一个元素上
<div :style="[baseStyles, overridingStyles]"></div> 复制代码
data以下:
baseStyles: { fontSize: '20px', color: 'blue' }, overridingStyles: { height: '80px' } 复制代码
HTML将被渲染为:
<div style="font-size: 20px; color: blue; height: 80px;"></div> 复制代码
react使用className用于指定css的class,react中不能直接为组件指定class。
react中className通常传值字符串常量或者字符串变量,不能传递数组或者对象语法。
(1)传字符串常量:
function NewComponent() { return <div className="container" >This is New Component.</div>; } 复制代码
(2)传字符串变量:
function NewComponent() { const newClass = 'conrainer' return <div className={newClass}>This is New Component.</div>; } 复制代码
(3)传递多个class,可使用es6的模板字符串实现:
function NewComponent() { const newClass = 'conrainer' return <div className={`${newClass} new-container`}>This is New Component.</div>; } 复制代码
当须要传递数组或者对象语法时,能够引入classnames库实现:
import classNames from 'classnnames'; function NewComponent() { const newClass = 'container'; return <div className={classNames(newClass, 'newContainer', { bar: true }, ['new-class', { c: true }])}>This is New Component.</div>; } 复制代码
html将被渲染为:
<div class="container newContainer bar new-class c">This is New Component.</div> 复制代码
一般不推荐将 style 属性做为设置元素样式的主要方式。在多数状况下,应使用 className 属性来引用外部 CSS 样式表中定义的 class。style 在 React 应用中多用于在渲染过程当中添加动态计算的样式。
style接收一个对象
const divStyle = { color: 'blue', backgroundImage: 'url(' + imgUrl + ')', }; function HelloWorldComponent() { return <div style={divStyle}>Hello World!</div>; } 复制代码
注意:样式不会自动补齐前缀。如需支持旧版浏览器,请手动补充对应的样式属性:
const divStyle = { WebkitTransition: 'all', // note the capital 'W' here msTransition: 'all' // 'ms' is the only lowercase vendor prefix }; function ComponentWithTransition() { return <div style={divStyle}>This should work cross-browser</div>; } 复制代码
组件的生命周期通常包含:初始化、挂载、更新、卸载四个大阶段,接下来分别看下vue和reac的生命周期。
vue生命周期图示:
vue生命周期包含:
注意:在这里操做数据很容易引发卡死。
react生命周期分为16.0以前和16.0以后:
react16.0以前生命周期以下:
(1)初始化
class Counter extends React.component{ constructor(props){ super(props); // 声明constructor时必须调用super方法 this.state = { count: 0 }; this.color = 'red'; } } 复制代码
(2)挂载阶段
不要在render里面修改state,会引发死循环致使卡死。
(3)更新阶段
当经过setState修改state或父组件从新render引发props更新,都会引发子组件的从新render。
通常咱们经过该钩子来优化性能,避免子组件没必要要的渲染。
注意:不能在此方法中调用
this.setState
注意:能够在
componentDidUpdate()
中直接调用setState()
,可是它必需被包裹在一个条件语句里,不然会致使死循环。
(4)卸载阶段
componentDidMount()
中建立的订阅等。注意:componentWillUnmount() 中不该调用 setState(),由于该组件将永远不会从新渲染。
react 16.0以后移除的生命周期函数:
可是为了向下兼容,react并未删除这三个生命周期,新增以 UNSAFE_
前缀为别名的三个函数 UNSAFE_componentWillMount()
、UNSAFE_componentWillReceiveProps()
、UNSAFE_componentWillUpdate()
。
新增的生命周期函数:
生命周期以下:
总结:
(1)初始化阶段保持不变
(2)挂载阶段:
getDerivedStateFromProps => render => componentDidMount
(3)更新阶段
getDerivedStateFromProps => shoudeComponentUpdate => render => getSnapshotBeforeUpdate => componentDidUpdate
(4)卸载阶段保持不变
接下来重点介绍getDerivedStateFromProps、getSnapshotBeforeUpdate两个方法。
(1)static getDerivedStateFromProps(props, state)
getDerivedStateFromProps 会在调用 render 方法以前调用,而且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,若是返回 null 则不更新任何内容。
当state的值在任什么时候候都取决于props的时候适用该方法。
示例以下:
class ScrollingList extends React.Component { constructor(props) { super(props); } static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.type !== prevState.type) { return { type: nextProps.type, }; } return null; } render() { return ( <div>{/* ...contents... */}</div> ); } } 复制代码
(2)getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)以前调用。它使得组件能在发生更改以前从 DOM 中捕获一些信息(例如,滚动位置)。今生命周期的任何返回值将做为参数传递给 componentDidUpdate()
。此用法并不常见,但它可能出如今 UI 处理中,如须要以特殊方式处理滚动位置的聊天线程等。应返回 snapshot 的值(或 null)。
示例以下:
class ScrollingList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { // 咱们是否在 list 中添加新的 items ? // 捕获滚动位置以便咱们稍后调整滚动位置。 if (prevProps.list.length < this.props.list.length) { const list = this.listRef.current; return list.scrollHeight - list.scrollTop; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { // 若是咱们 snapshot 有值,说明咱们刚刚添加了新的 items, // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。 //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值) if (snapshot !== null) { const list = this.listRef.current; list.scrollTop = list.scrollHeight - snapshot; } } render() { return ( <div ref={this.listRef}>{/* ...contents... */}</div> ); } } 复制代码
vue与react在针对事件处理上的用法上也有较大不一样。
vue中使用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。一般使用v-on接收一个须要调用的方法名称。
(1)直接绑定方法,不传任何参数,回调函数参数是浏览器事件event对象。
示例:
<div @click="greet">Greet</div> 复制代码
method:
methods: {
greet(event) {
console.log(event);
}
}
复制代码
(2)内联调用方法
示例:
<div @click="greet('hello')">Greet</div> 复制代码
method:
methods: {
greet(message) {
this.message = message;
}
}
复制代码
有时也须要在method中访问原生DOM事件,能够将$event显式传入method中。
<div @click="greet('hello', $event)">Greet</div> 复制代码
method:
methods: {
greet(message, event) {
this.message = message;
}
}
复制代码
(3)事件修饰符和按键修饰符
Vue.js为事件添加了事件修饰符和按键修饰符(我的感受这个是vue作的很好的点,让用户更加聚焦数据逻辑,无需花费更多的精力处理DOM事件细节)。
Ⅰ. 事件修饰符
在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是很是常见的需求。为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符,修饰符是由点开头的指令后缀来表示的。
示例:
<!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件再也不重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符能够串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即内部元素触发的事件先在此处理,而后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div> <!-- 点击事件将只会触发一次 --> <a v-on:click.once="doThis"></a> <!-- 滚动事件的默认行为 (即滚动行为) 将会当即触发 --> <!-- 而不会等待 `onScroll` 完成 --> <!-- 这其中包含 `event.preventDefault()` 的状况 -- <div v-on:scroll.passive="onScroll">...</div> 复制代码
Ⅱ. 按键修饰符
在监听键盘事件时,咱们常常须要检查详细的按键。Vue 容许为 v-on 在监听键盘事件时添加按键修饰符。 你能够直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来做为修饰符。
① 按键码
Vue 提供了绝大多数经常使用的按键码的别名
使用 keyCode 特性也是容许的:
<input v-on:keyup.13="submit"> 复制代码
② 系统修饰键
能够用以下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
③ .exact 修饰符
.exact 修饰符容许你控制由精确的系统修饰符组合触发的事件。
<!-- 有且只有 Ctrl 被按下的时候才触发 --> <button @click.ctrl.exact="onCtrlClick">A</button> 复制代码
④ 鼠标按钮修饰符
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
关于v-on处理事件的好处:
1.扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
2.由于你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码能够是很是纯粹的逻辑,和 DOM 彻底解耦,更易于测试。
3.当一个 ViewModel 被销毁时,全部的事件处理器都会自动被删除。你无须担忧如何清理它们。
React 元素的事件处理和 DOM 元素的很类似,可是有一点语法上的不一样:
(1)事件处理程序不传参数
在class组件中使用回调函数,须要显式绑定this或者使用箭头函数。
不传参数时,默认参数是e,这是一个合成事件。React 根据 W3C 规范来定义这些合成事件,因此你不须要担忧跨浏览器的兼容性问题。
在React中不能经过返回false的方式阻止默认行为,你必须显式的使用preventDefault。
显示绑定this:
class NewComponent extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); // 显示绑定this.handleClick } handleClick(e) { e.preventDefault(); console.log(e.target); } render() { return <div onClick={this.handleClick}>Click me</div>; } } 复制代码
箭头函数:
class NewComponent extends React.Component { constructor(props) { super(props); } handleClick = (e) => { e.preventDefault(); console.log(e.target); }; render() { return <div onClick={this.handleClick}>Click me</div>; } } 复制代码
(2)事件处理程序传递参数
一般咱们会为事件处理函数传递额外的参数,有两种方式向事件处理函数传递参数:
Ⅰ. 箭头函数传递参数
经过箭头函数的方式,事件对象e必需显示的进行传递。
class NewComponent extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick(e, message) { e.preventDefault(); console.log(message); }; render() { return <div onClick={(e) => this.handleClick(e, 'hello')}>Click me</div>; } } 复制代码
Ⅱ. 经过bind形式传递参数
e做为第二个参数传递,事件对象以及更多的参数将会被隐式的传递。
class NewComponent extends React.Component { constructor(props) { super(props); } handleClick(message, e) { // e做为第二个参数 e.preventDefault(); console.log(message); }; render() { return <div onClick={this.handleClick.bind(this, 'hello')}>Click me</div>; } } 复制代码
以上就是博主关于react和vue的一些对比以及我的思考的上部,以为有收获的能够关注一波,点赞一波,码字不易,万分感谢,后续中部和下部会尽快持续更新,感谢关注。