一、背景
Vue
Google前端工程师尤雨溪于2014年创建了这个框架,Vue是一套用于构建用户界面的渐进式框架,与其他大型框架不同的是,Vue被设计为可以自底向上逐层应用,Vue核心时只关注视图层,不仅易于上手,还便于与第三方库或既有的项目整合
React
与Vue不同,react库是由FaceBook创建的,最初是为了FackBook广告流量管理创建的,那是FackBook遇到了维护和编码方面的问题,它以动态创建和交互UI的能力而闻名
二、核心思想
vue和react都是推崇组件式的开发理念,但是在设计的核心思想上有很大的差别
Vue
Vue的整理思想仍然是拥抱经典的html(结构)+css(表现)+jd(行为)的形式,Vur鼓励开发者使用template模板,并提供指令供开发着使用(v-if、v-show、v-for等等),因此开发vue应用又是会有一种在写经典web应用(结构、表现、行为分离)的感觉。另一方面,在针对组件数据上,Vue2.0通过Object.defineProperty对数据做到了更细致的监听,景区实现组件级别的更新。
React
react整体上是函数式的思想,组件使用jsx语法,all in js,将html与css全部融入javaScript,jsx语法相对来说更加灵活,刚开始从Vue转React的时候我也不是很适应,觉得react的写法很自由。当组件调用setState或props变化的时候,组件内部render会重新渲染,子组件也会随之重新渲染,可以通过shouldComponentUpdate或者PrueComponent可以避免不必要的重新渲染
三、组件形式
Vue
vue组件是使用.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
react推荐使用jsx或者js文件来表示组件,react支持class组件和function组件,react中是使用{}包裹变量,且需要注意的是,组件名称必须大写字母开头,React会将以小写字母开头的组件视为原生Dom标签,例如<div />代表html的div标签,而<Welcom />则代表一个组件,并且需在作用域使用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组件
import React, { useState } from 'react';
function NewComponent() {
const [name, setName] = useState('');
return (<div>{name}</div>);
}
export default NewComponent;
四、数据管理(props、data、state)
组件中数据管理通常包含2部分,来自父组件的数据props与自身的数据
vue与react中的props都是单项数据流的,父级prop的更新会向下流动到子组件中,但是反过来不行。props可以是数组或对象,用于接收来自父组件的数据。
Vue
Props
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>
data
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
react中的props也与vue一样可以传递静态或动态props,静态props一般传递字符串
函数组件和class组件都可以使用props,函数组件使用props参数获取父组件传递的props
1)函数组件获取props
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
2)class组件获取props(通过this.props获取)
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
const { name } = this.props;
return <div>{name}</div>;
}
}
动态的props
<Welcome name={name} />
state
react中使用state来管理组件内的数据,hooks的出现使用函数组件也具备管理state的能力
1)class组件中的state
class组件在构造函数(constructor)中定义组件内数据(state),修改数据必须通过setSate修改,不能直接修改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有以下需要注意点:
-
setState更新是异步的,但是在setTimeout和原生时间中式同步的
-
setState更新式组件部分数据,更新机制式合并数据
-
当需要使用上一个state值时,可以让setSate()接收一个函数而不是一个对象。这个函数用上一个state作为第一个参数,将此次更新被应用时的props作为第二个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
2)function组件中的useState(hook)
react16.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有一下注意点:
- 与class组件中的setState方法不同,useState不会自动合并更新对象
- 只能在函数最外层调用Hook,不要再循环、条件判断中胡总子函数中调用
- 只能再React的函数组件或者自定义hook中调用hook,不要在其他JavaScript函数中调用
五、组件数据交互
组件数据交互指的是父子组件、兄弟组件、跨层组件之间传递数据。兄弟组件之间可以通过事件总线或者通过父组组件传递数据
1.父子组件数据交互(props+自定义事件 VS props+回调)
vue:props+自定义事件
react:props+回调
Vue
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
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>;
}
2.跨组件数据交互(provide/inject VS Context)
vue和react都支持跨组件传递数据,vue中主要通provide/inject实现,react中主要是通过Context实现
Vue
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绑定并不是可响应式的,这是刻意为之,然而,如果你传入一个可监听的对象,那么其对象的属性还是可响应的。
react
Context提供了一个无需为每层手动添加props,就能在组件树间进行数据传递的方法
import React,{useState} from 'react'
//创建Context对象
const MyContext=React.createContect({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的两种方式:
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
对于css中class与style处理上,vue与react也存在较大差异
Vue
vue对class与style特意做了增强,可以传字符串、对象、数组
class
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>
style
style用来绑定内联样式,支持传对象、数组、使用需要添加浏览器引擎的css属性时,如transform、Vue.js会自动侦测并添加相应的前缀
1)传对象,css属性名可以用驼峰式或者短横线分割
<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,overriingStyles]"></div>
data如下:
baseStyles: {
fontSize: '20px',
color: 'blue'
},
overridingStyles: {
height: '80px'
}
HTML将被渲染为:
<div style="font-size: 20px; color: blue; height: 80px;"></div>
react
react使用className用于指定css的class,react不能直接为组件指定class
className
react中的饿className一般传值字符串常量或者字符串变量,不能传递数组或者对象语法
1)传递字符串常量
function NewComponent(){
return <div className="container">this is a new Component.</div>
}
2)传递字符串变量
function NewComponent(){
const newClass='container'
return <div className={newClass}>this is a new Component.</div>
}
3)传递多个class,可以使用es6的模板字符串实现
function NewComponent(){
const newClass="container"
return <div className={`${newClass} new-container`}>.....</div>
}
4)如果需要传递数组或者对象语法时,可以引入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
通常不推荐将style属性作为设置元素样式的主要方式。在多数情况下,应使用className属性来引用外部css样式表中定义的class。style在react应用中多用于在渲染过程中添加动态计算样式
const divStyle={
color:'blue',
backgroundImage:'url('+imgUrl+')'
}
function HelloWordComponent(){
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和react的生命周期
Vue
vue生命周期图示:
React
react生命周期氛围16.0之前和16.0之后
16.0之前
1)初始化阶段:constructor
是class组件默认的方法,常用来初始化state或者设置属性等
class Counter extends React.component{
construtor(props){
super(props)
this.state={
count:0
}
this.color='red'
}
}
2)挂在阶段
- componentWillMount():组件挂在之前调用,并且只会调用一次
- render:render是React组件必须定义的生命周期函数,用来渲染DOM。 并必须返回一个React元素(描述组件,即UI),不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM
注意:不要在render里面修改state,会引起死循环导致卡死
- componentDidMount():组件挂载完毕之后调用,在这个阶段可以获取真实Dom元素,宠用来发起异步请求获取数据
3)更新阶段:
但通过setState修改state或父组件重新render引起props更新,都会引起子组件的重新render
- componentWillReceiveProps(nextProps):props发生变化以及父组件重新渲染时都会触发该生命周期函数。在该阶段可以通过参数nextProps获取变化后的props参数,通过this.props访问之前的props,该生命周期内可以进行setState
- shouldComponentUpdate(nextProps,nextState):组件每次setState或者父组件chongxinrender都会引起子组件render,可以使用该钩子比较nextProps,nextState以及当前组件的this.props,this.state的状态来判断是否需要重新渲染。默认返回true(会重新渲染);返回false则不触发渲染。
一般我们通过该钩子来优化性能,避免子组件不必要的渲染
- componentWillUpdate(nextProps,nextState):当组件收到新的props或者state时,就会渲染之前调用,使用此作为更新发生之前执行准备更新的机会。初始渲染不会调用该方法
注意:不能在此方法中调用
this.setState
- componentDidUpdate(prevProps,prevState):此方法在组件更新后被调用。首次渲染不会执行才方法,当组件更新后,可以在此处对DOM进行操作
注意:可以在componentDidUpdate()中直接调用setState(),但是它必须被包裹在一个条件语句中,否则会导致死循环
4)卸载阶段
- componentWillUnmount():会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如:清除timer,取消网络请求或清除在componentDidMount()中创建的订阅等
注意:componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。
16.0之后
react16.0之后移除的生命周期函数:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
但是为了向下兼容,react并未删除这三个生命周期函数,新增“UNSAFE_”为前缀别名和的三个函数:UNSAFE_componentWillMount()
、UNSAFE_componentWillReceiveProps()
、UNSAFE_componentWillUpdate()
新增的生命周期函数
static getDerivedStateFromProps(nextProps,preState)
getSnapshotBeforUpdate(nextProps,prevState)
react16.0更新之后的生命周期函数总结:
(1)初始化阶段保持不变
(2)挂载阶段:getDerivedStateFromProps=>render=>componentDidMount
(3)更新阶段:getDerivedStateFromProps=>shouldComponentUpdate=>render=>getSnapshotBeforeUpdate=>componentDidUpdate
(4)卸载阶段保持不变
八、事件处理
vue和react在事件处理上用法也有所差异
Vue
vue中使用的是v-on指令的方式为元素绑定时间,并在出发时运行一些JavaScipt代码,通常使v-on接受一个需要调用的方法名称
1)直接绑定方法,不传递任何参数,回调函数参数是浏览器事件event对象
<div @click="greet">Greet</div>
method:
methods: {
greet(event) {
console.log(event);
}
}
2)内联调用方法
<div @click="greet('hello')">Greet</div>
methods: {
greet(message) {
this.message = message;
}
}
有时候也需要在method中访问原生DOM事件,可以讲$event显示传入method中
<div @click="greet('hello', $event)">Greet</div>
methods: {
greet(message, event) {
this.message = message;
}
}
3)事件修饰符和按键修饰符
Vue为事件添加了事件修饰和按键修饰符
事件修饰符:
在事件处理程序中调用event.preventDefault()或者event.stopPropagation()是非常常见的需求,为了解决这个问题,Vue为v-on提供了事件修饰符,修饰符是由.开头的指令后缀来表示的。
- .stop:阻止事件继续传播
- .prevent:阻止事件默认行为
- .self:当前元素触发时才触发时间处理函数
- .once:事件只触发一次
- .passive:告诉浏览器你不想阻止事件默认行为,不能和prevent一起使用
<!-- 阻止单击事件继续传播 -->
<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>
4)按键修饰符
在监听键盘事件时,我们经常要检查详细的按键。vue允许为v-on在监听键盘时间时添加修饰符,你可以直接将KeyBoardEvent.key暴露的任意有效按键名转换为kebab-case来做为修饰符
按键码:
- .enter
- .tab
- .delete
- .esc
- .space
- .up
- .dowm
- .left
- .right
使用keyCode特性也是允许的
<input v-on:keyup.13="submit">
关于v-on处理事件的好处:
1.扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
2.因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
3.当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
React
react元素的事件处理和DOM元素的很相似,但是有一点语法上的不同:
- react事件的命名采用驼峰,而不是纯小写
- 使用JSX语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
1)事件处理程序不传递参数
在class类组件中使用回调函数,需要显示绑定this或者使用箭头函数
不传递参数时,默认参数是e,这是一个合成时间。React根据W3C规范来定义这些合成事件,所以你不需要担心跨浏览器的兼容问题
在React中不能同返回false的方式阻止默认行为,你必须显示的使用preventDefault
- 显示绑定this的方式
class NewComponent extends React.component{
construtor(props){
super(props)
this.handleClick=this.handleClick.bind(this);//显示绑定this.handlClick
}
handleClick(e){
e.preventDefault();
console.log(e.target);
}
render(){
return (
<div onClick={this.handleClick}>Click Me</div>
)
}
}
- 箭头函数的方式
class NewComponent extends React.component{
construtor(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{
construtor(props){
super(props)
this.handleClick=this.handelClick.bind(this)
}
handleClick=(e)=>{
e.preventDefault();
console.log(e.target);
}
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>;
}
}