前言
本人原本是一个React开发者,由于最近项目需要维护,技术栈需要会使用Vue,我在之前也从来没有看过Vue的文档,就花了一个周末的时间简单学习了一下Vue的知识点。这里分享一点Vue的快速入门笔记,本文也同样适用于目前只会Vue,但想快速学习React的同学。
实例
Vue实例
每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始,分别添加Vue的周边生态:路由 vue-router 、国际化 vue-i18n 、状态管理 vuex ,和将要渲染的节点#app 。
new Vue({
el: '#app',
router, //vue-router
i18n, //vue-i18n
store, //vuex
render: h => h(App)
})
React实例
React也是同理,但是它是将组件一层层包裹,通过context上下文和props来传递,添加React的周边生态:路由 react-router-dom 、国际化 react-i18next 、状态管理 react-redux ,同时使用 react-dom提供的ReactDom 来将页面渲染到相应的#app节点上。
ReactDom.render(
<StoreProvider>
<I18n>
<BrowerRouter>
{route}
</BrowerRouter>
</I18n>
</StoreProvider>,
document.getElementById('app'),
)
模板
Vue模板
Vue的一个页面构成比较简单明了,更像是原来的html结构。
emplate里编写标签来生成dom节点,script里编写JavaScript来处理逻辑,style里编写css来处理样式。
<template>
<div class='text'>
{{msg}}
</div>
</template>
<script>
export default {
name:'hello',
//定义成函数是为了组件复用
data:function(){
return{
msg:'Hello Vue!'
}
},
}
</script>
<style>
.text{
color:'red';
}
</style>
React模板
对于比React来看,它是纯JavaScript编写代码来渲染dom和处理逻辑,定义state的值来控制页面的渲染,render里return标签来生成dom节点。
//react的jsx语法
class Hello extends React.Component{
state = {
msg:'hello React!'
}
render(){
const {msg} = this.state;
return(
<div className = 'text'>
{msg}
</div>
)
}
}
对比一下语法:
- Vue可以直接在模板里使用 msg属性 而不是 this.data.msg 这样使用,模板里使用 双括号。
- React定义的 msg属性 放在 this.state 上的,但是却需要用 this.state.msg 取值,模板里使用 单括号。
具体Vue实例化为什么可以直接能获取到data内的数据,可以参考这个回答 为什么Vue实例化后,通过this.能获取到data内的数据
常用指令
v-if/v-else/v-else-if
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回true值的时候被渲染, 也可以用 v-else-if 继续做判断,最后再用 v-else 做最后处理。
//Vue的模板语法 这里只有v-else的文字会显示出来
<template>
<div id='app'>
<div v-if='flag === 0'>
由于v-if里的条件判断结果是false,故现在不能看到文字
</div>
<div v-else-if = 'flag === 2'>
v-else-if必须紧跟v-if后面,否则会无效
</div>
<div v-else>
v-else必须紧跟v-if或者v-else-if后面,否则会无效
</div>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
flag:1
}
},
}
</script>
<style>
</style>
v-show
另一个用于根据条件展示元素的选项是 v-show 指令。
//Vue的模板语法
<template>
<div id='app'>
<div v-show='flag'>
由于v-show里的条件判断结果是true,故现在可以看到文字
</div>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
flag:true
}
},
}
</script>
<style>
</style>
这里就会发现 v-if 和 v-show 都可以达到隐藏文字和显示文字的作用,但是两者还是有区别的,使用 v-if 时当其值是false时,dom节点不会被渲染,但是使用 v-show 时无论其值是true还是false,dom节点都会被渲染。
再讲一下 v-if 和 v-show 的原理:
- v-if 就像if和else一样动态地创建元素,当v-if为false时会将 dom节点移除。所以当v-if控制的是组件,切换过程中条件块内的事件监听器和子组件会被 销毁和重建 ,会触发组件和子组件的生命周期。
- v-show 初始化时为false时会添加 style:'display:none' ,为true时会移除 display:none,不管渲染条件是什么,元素总是会被渲染,然后再进行css的操作。
React模拟v-if和v-show
知道了原理再来看看React模拟 v-if 和 v-show 的实现,React对于 v-if 一般可以用 三目表达式 表示,对于 v-show 可以对 style 直接赋值切换
//React
class Hello extends React.Component{
state = {
flag:'1',
show: false
}
render(){
const { flag,show } = this.state;
return(
<div id='app'>
//对于v-if的模拟
{
flag === '0' ?
(
<div>
类似于Vue里的v-if
</div>
)
: flag === '2' ?
(
<div>
类似于Vue里的v-else-if
</div>
)
:
(
<div>
类似于Vue里的v-else
</div>
)
}
//对于v-show的模拟
<div style={show ? {} : {display:'none'}}>
类似于Vue里的v-show
</div>
</div>
)
}
}
v-bind
一些指令能够接收一个 “参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 dom属性。
//Vue的模板语法
<template>
<div id='app'>
<a v-bind:href='url' >
跳转到百度
</a>
//缩写
<a :href='url' >
跳转到百度
</a>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
url:'www.baidu.com'
}
}
}
</script>
<style>
</style>
其实 v-bind 类似于React中的绑定值
class Hello extends React.Component{
state = {
url:'www.baidu.com'
}
render(){
const {url} = this.state;
return(
<div id='app'>
<a href={url}>
跳转到百度
</a>
</div>
)
}
}
v-on
用于指出一个指令应该以特殊方式绑定。例如,v-on:click.prevent 修饰符告诉 v-on 指令对于触发点击的事件时并调用 event.preventDefault(),常用修饰符除.prevent以外常用的还有:
- .stop - 调用 event.stopPropagation();
- .once - 只触发一次回调;
//Vue的模板语法
<template>
<div id='app'>
<span>{{ num }}</span>
<button v-on:click.prevent="add">增加</button>
//缩写
<button @click.prevent="add">增加</button>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
num:0
}
},
methods:{
add(){
this.num++;
}
}
}
</script>
<style>
</style>
但是在React里,事件驱动还是以on开头进行编写,如onClick、onChange等
class Hello extends React.Component{
state = {
num:0
}
add = e => {
e.preventDefault();
this.setState((state)=>({
num: state.num+1
}))
}
render(){
const {num} = this.state;
return(
<div id='app'>
<span>{{ num }}</span>
<button onClick={this.add}>
增加
</button>
</div>
)
}
}
Vue的计算属性与侦听器
Vue模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,所以任何复杂逻辑,应当使用计算属性。
<template>
<div id='app'>
//bad example
<div id='bad'>
{{msg.split('').reverse().join('')}}
</div>
//good example
<div id='good'>
{{reverseMsg}}
</div>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
msg:'Hello'
}
},
computed:{
reverseMsg: function(){
return this.msg.split('').reverse().join('')
}
}
}
</script>
绑定普通属性一样在模板中绑定计算属性,Vue 知道 reversedMsg 依赖于 this.msg ,因此当 this.message 发生改变时,所有依赖 reversedMsg 的绑定也会更新。
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性 watch ,可以用来监听值的变化来做一些事情,但是如果有一些数据要随着其他数据变动而变动时,应该使用 computed 而不是 watch。
<script>
export default {
name:'hello',
data:function(){
return{
a:{
b:{
c:5
}
}
}
},
watch: {
c:{
//属性变化时触发的事件
handler:function(value,oldValue){
console.log('新数据:'+value,'原数据:'+oldValue)
},
//该回调会在任何被侦听的对象的属性改变时被调用,不论其被嵌套多深
deep:true,
//该回调将会在侦听开始之后立马被调用
immediate:true
}
}
}
</script>
在React通过 state 里的值来进行做计算属性,可以将变量单独定义在 render 中。但是通过 state 的值来进行做 侦听 的事情,应当是在 更新阶段 执行,这里放到生命周期处再做介绍。
列表渲染
Vue的列表渲染
我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。
//Vue的模板语法
<template>
<div id='app'>
<ul id="example">
<li
v-for="(item,index) in items"
:key="item.message"
>
{{ item.message }} - {{index}}
</li>
</ul>
</div>
</template>
<script>
export default {
name:'hello',
//定义成函数是为了组件复用
data:function(){
return{
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
},
}
</script>
//React
class Hello extends React.Component{
state = {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
render(){
const {items} = this.state;
return(
<div id='app'>
<ul id="example">
{
items.map((item,index)=>{
return(
<li
key={item.message}
>
{ item.message } - {index}
</li>
)
})
}
</ul>
</div>
)
}
}
数组更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
//Vue的模板语法
<template>
<div id='app'>
<span>{{ arr }}</span>
<button @click="arr.pop()">出栈</button>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
arr:[1,2,3,4,5]
}
},
}
</script>
当点击按钮以后,数组每次就会 出栈一个元素,并且视图发生更新,但是当使用Array的filter()、concat() 和 slice() 等api时,它们将不会改变原数组,而是返回一个新数组,直接使用时不会更新视图,这里可以用新数组去替换原数组。
//Vue的模板语法
<template>
<div id='app'>
<span>{{ arr }}</span>
<button @click="filterArray">筛选大于等于3的元素</button>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
arr:[1,2,3,4,5]
}
},
methods:{
filterArray(){
this.arr = this.arr.filter.filter(function(item){
return item >= 3
})
}
}
}
</script>
React的列表渲染
React的 setState 机制是直接赋值,所以使用push、pop等改变原数组的操作时,需要先取 原始值 再赋值给 state。
class Hello extends React.Component{
state = {
arr:[1,2,3]
}
addArr = ()=>{
//使用浅拷贝复制数组再执行添加
const data = this.state.arr.concat();
data.push(4)
this.setState({
arr: data
})
}
filterArr = ()=>{
//由于filter返回一个新数组,这里正好返回给arr
this.setState((state)=>({
arr: state.arr.filter(item => item > 2)
}))
}
render(){
const {arr} = this.state;
return(
<div id='app'>
<span>{ arr }</span>
<button onClick={this.filterArr}>
添加数字
</button>
<button onClick={this.filterArr}>
筛选大于等于3的数
</button>
</div>
)
}
}
组件传值与插槽
Vue的组件传值与插槽
Vue 实现了一套内容分发的 API,将 <slot> 元素作为承载分发内容的出口。
Vue的子组件传递给父组件时,如果子组件触发的是 方法,则使用 this.$emit(eventName, args) 用来做自定义事件。如果子组件获取父组件的 属性值, 则使用 props 进行定义 类型 和 默认值。
//Vue子组件
<template>
<div>
<div @click="say">
{{msg}}
//插槽,类似于react里的{children}
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name:'Hello',
//定义props里的值类型和默认值
props:{
msg:{
type:String,
default:''
}
},
methods:{
say(){
console.log('触发子方法给父组件')
//给父组件的自定义方法
this.$emit('say')
}
}
}
</script>
//Vue父组件
<template>
<div>
<Hello :msg="msg" @say="say">
<div>插槽文字</div>
</Hello>
</div>
</template>
<script>
import Hello from "./Hello.vue";
export default {
name:'App',
//定义props里的值类型和默认值
data:function(){
return{
msg:'传递给子组件的值'
}
},
//注册组件
components: {
Hello,
},
methods:{
say(){
console.log('Hello')
}
}
}
</script>
React的组件传值与插槽
React 的插槽比较简单,就是一个包裹 this.props.children 就可以了。具体原因主要是React的每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖,相当于是自带插槽。
React 的子组件向父组件传递时,无论是 触发方法 还是获取父组件传递下来的 属性值 ,都是使用 this.props.[protoitype] 的方式命名。
class Child extends React.Component{
say = ()=>{
console.log('触发子方法');
//直接定义say方法到父组件里提供调用
this.props.say();
}
render(){
const {msg,children} = this.props;
return(
<div id='app'>
<div onClick={this.say}>
{msg}
<div>
{children}
</div>
</div>
</div>
)
}
}
class Parent extends React.Component{
state = {
msg: '传递给子组件的值'
}
say = ()=>{
console.log('父组件里触发方法');
}
render(){
const {msg} = this.state;
return(
<div>
<Child msg={msg} say={this.say}>
<div>
插槽文字
</div>
</Child>
</div>
)
}
}
生命周期
Vue生命周期
Vue的生命周期比较于React来说相对简单点,Vue大体划分四个阶段:
- 初始化:beforeCreate、created
- 渲染:beforeMount、mounted
- 更新:beforeUpdate、updated
- 卸载:beforeDestroy、destroy
还有三个生命周期较少使用,在updated后触发:
- activated ( keep-alive 组件激活时调用 )
- deactivated ( keep-alive 组件停用时调用 )
- errorCaptured (当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。)
<script>
new Vue({
el:'#app',
data:{
title:'hello word',
},
methods:{
top(){
this.
}
},
beforeCreate(){
//不能获得实例化data里的值
//页面加载出来就会执行
console.log(this.title)
console.log('创建之前')
//页面没加载出来,可以写加载的loading图
},
created(){
console.log(this.title)
console.log('创建之后')
},
beforeMount(){
//把当前实例化的Vue挂载到绑定的DOM元素上
//this.$el是获取当前实例化内的所有DOM节点
//此时DOM中的变量没有被渲染
//页面加载出来就会执行
console.log(this.$el)
console.log('挂载之前')
},
mounted(){
//此时DOM内的变量已经被渲染
console.log(this.$el)
console.log('挂载之后')
},
beforeUpdate(){
//数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
//你可以在这个钩子中进一步地更改状态,
//这不会触发附加的重渲染过程。
//改变的是DOM元素里的数据变更
//data里的数据变更不会触发
//页面加载出来不会执行,当数据变更才会执行
console.log(document.querySelector('#val').innerHTML)
console.log('更新之前')
//该钩子在服务器端渲染期间不被调用。
},
updated(){
//此时的DOM已经更新
//避免在此期间更改状态,因为这可能会导致更新无限循环。
console.log(document.querySelector('#val').innerHTML)
console.log('更新之后');
},
beforeDestroy(){
//实例销毁之前调用。在这一步,实例仍然完全可用。
console.log('催毁之前');
},
destroy(){
//Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定
// 所有的事件监听器会被移除,所有的子实例也会被销毁。
console.log('摧毁之后')
}
})
</script>
关于网络请求放在哪个生命周期里?
可以放置于created、beforeMount、mounted中进行,因为这三个生命周期里 this.data 已经被创建,可以将服务端返回数据进行赋值。
但是目前Vue的项目多用于 ssr服务端渲染 ,ssr 不支持 beforeMount、mounted生命周期函数,故推荐在 created 里调用请求,并且它能更快获取到服务端数据,减少loading时间。
React生命周期
React(16.4+)的生命周期大体划分三个阶段:
- 挂载:constructor、getDerivedStateFromProps、render、componentDidMount
- 更新:getDerivedStateFromProps、shouldComponentUpdate、render、getSnapshotBeforeUpdate、componentDidUpdate
- 卸载:componentWillUnmount
具体生命周期实例参考官网
React的网络请求一般放置于 componentDidMount 生命周期中,在 render渲染 完成以后执行。
回到 侦听 和 计算属性,React可以直接在render中定义 计算属性,因为 state 发生更新以后,整个 render 会重新渲染。侦听 可以获取上一个值和最新的值,可以在 componentDidUpdate 中手动处理。
class Child extends React.Component{
state = {
msg:'hello',
num:0
}
componentDidUpdate(prevProps,prevState){
if(prevState.num !== this.state.num){
console.log(prevState.num,'原来的值')
console.log(this.state.num, '最新的值')
}
}
render(){
const {msg} = this.state;
const reverseMsg = msg.split('').reverse().join('')
return(
<div>
<div>
{reverseMsg}
</div>
</div>
)
}
}
Vue的v-modal机制
可以用 v-model 指令在表单 input、textarea 及 select 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
//Vue的模板语法
<template>
<div id='app'>
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
</div>
</template>
<script>
export default {
name:'hello',
data:function(){
return{
message:''
}
},
}
</script>
修饰符: .lazy、.number、.trim
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
<!-- 自动将用户的输入值转为数值类型 -->
<input v-model.number="age" type="number">
<!-- 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">
自定义 v-modal,前面已经知道了父子组件的传值与触发方法和v-modal的原理,现在来自己写一个自定义组件使用v-modal的例子
//Vue子组件
<template>
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
/>
</template>
<script>
export default {
name:'base-checkbox',
//允许一个自定义组件在使用 v-model 时定制 prop 和 event
model:{
prop:'checked',
event:'change'
},
//定义props里的值类型和默认值
props:{
checked: Boolean
},
}
</script>
//使用
<base-checkbox v-model="lovingVue"></base-checkbox>
Vue自定义指令
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许 注册自定义指令,看一个官方的例子
Vue.directive 里会携带多个钩子函数:
- bind :只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
再来看看钩子函数的参数
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为{ foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
<div v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
显示结果如下:
再写一个input框防抖的例子
Vue.directive('debounce', {
inserted: function (el, binding) {
let timer;
el.addEventListener('click',()=>{
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
binding.value()
},1000)
})
}
})
<template>
<button v-debounce="debounceClick">防抖</button>
</template>
<script>
export default {
methods: {
debounceClick () {
console.log('只触发一次')
}
}
}
</script>
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!