vue3入门

558 阅读11分钟

一: Vue3.0六大亮点

1. 性能比Vue2.x快1.2-2倍

1.1、diff方法优化

  • vue2中的虚拟dom是进行全量的对比
  • vue3新增了静态标记(PathchFlag),在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点。并且可以通过flag的信息得知当前节点要对比的具体内容。

1.2、静态提升

  • Vue2中无论元素是否参与更新,每次都会重新创建,然后在渲染
  • Vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可

1.3、事件监听器缓存

  • 默认情况下Onclick会被视为动态绑定,所以每次都会去追踪它的变化。
  • 但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。

1.4、ssr渲染

2. 按需编译,体积比Vue2.x更小

3. Composition API:组合API

4. 更好的TS支持

5. 暴露了自定义渲染API

6. 更先进的组件(Fragment、Teleport、Suspense)

二: 安装vue官方脚手架以及创建项目

1. 通过Vue-cli创建项目

1.1 安装vue-cli(只需安装一次)

yarn global add @vue/cli
npm install -g @vue/cli
cnpm install -g @vue/cli

1.2 通过vue-cli创建项目

vue create <project-name>
cd <project-name>
npm run serve 或者 yarn serve

会报错❌ ERROR  Error: Cannot find module 'vue-loader-v16/package.json'
解决办法:更新node版本 或者 cnpm i vue-loader-v16之后在npm run serve

2. 通过Vite脚手架创建项目

1. 使用npm创建

npm init vite-app <project-name>
cd <project-name>
npm install 
npm run dev

2. 使用yarn创建

yarn create vite-app <project-name>
cd <project-name>
yarn
yarn dev

3. 通过webpack创建

git clone https://github.com/vuejs/vue-next-webpack-preview.git <project-name>
cd <project-name>
npm install
npm run dev

三:绑定数据、循环

1. 绑定

1.1 {{}}绑定数据

<template>
  <div>绑定普通数据:{{msg}}</div>
  <div>绑定对象中的数据:{{userInfo.name}} --- {{userInfo.age}}</div>
</template>
<script>
  export default{
    data(){
      return{
        msg: '你好vue',
        userInfo:{
          name: '张三',
          age: 20
        }
      }
    }
  }
</script>

//绑定普通数据:你好vue
//绑定对象中的数据:张三 --- 20

1.2 v-html绑定html

<template>
  <p>绑定html: <span v-html="htmlText"></span></p>
</template>
<script>
  export default{
    data(){
      return{
        htmlText: '<h2>我是一个h2标签</h2>'
      }
    }
  }
</script>

1.3 v-bind绑定属性

<template>
  <img v-bind:src="logoSrc" alt="itying">
  //v-bind:src的缩写
  <img :src="logoSrc" alt="itying"><div v-bind:title="title">鼠标放上去试试</div>
  <div :title="title">鼠标放上去试试</div>
</template>
<script>
  export default{
    data(){
      return{
        logoSrc: 'https://www.itying.com/themes/itying/images/logo.gif',
        title: '自定义属性值'
      }
    }
  }
</script>

1.4 ⚠️v-bind动态参数

<template>
  <a v-bind:[myHref]="'http://www.baidu.com'">跳转到百度</a>
  <br>
  <a v-bind:[myHref]="myLink">跳转到itying</a>
</template>
<script>
  export default{
    data(){
      return{
        myHref: 'href',
        myLink: 'http://www.itying.com'
      }
    }
  }
</script>

// <a href="'http://www.baidu.com'">跳转到百度</a>
// <a href="http://www.itying.com">跳转到itying</a>

2. 循环(vue3中必须动态绑定key值,否则报错)

2.1 循环数组

<template>
  <ul>
    <li v-for="(item,index) in list" :key="index">{{item}} --- {{index}}</li>
  </ul>
  <ul>
    <li v-for="(item,index) in list1" :key="index">{{item.title}} --- {{index}}</li>
  </ul>
  <ul>
    <li v-for="(item,index) in list2" :key="index">
      {{item.cate}}
      <ol>
        <li v-for="(i,k) in item.list" :key="k">
          {{i.title}}
        </li>
      </ol>
    </li>
  </ul>
</template>
<script>
  export default{
    data(){
      return{
        list: ['李总','刘总','王总'],
        list1: [
          {
            title: '标题1'
          },
          {
            title: '标题2'
          },
          {
            title: '标题3'
          },
        ],
        list2: [
          {
            cate: "国内新闻",
            list: [
              {
                title: '国内新闻1'
              },
              {
                title: '国内新闻2'
              }
            ]
          },
          {
            cate: "国际新闻",
            list: [
              {
                title: '国际新闻1'
              },
              {
                title: '国际新闻2'
              }
            ]
          }

        ]

      }
    }
  }
</script>

2.2 循环对象

<template>
<ul>
  <li v-for="(value,key,index) in myObject" :key="key">
    {{key}}:{{value}}--{{index}}
  </li>
</ul>
</template>
<script>
  export default{
    data(){
      return{
        myObject:{
          title: '这是标题',
          author:'我是作者',
          publishAt: '2020-02-03'
        }
      }
    }
  }
</script>

title:这是标题--0
author:我是作者--1
publishAt:2020-02-03--2

四:模版中类、样式的绑定

1. 类的绑定

1.1 v-bind:class绑定字符串

<template>
  <div :class="myClass">绑定单个类名</div>
</template>
<script>
  export default{
    data(){
      return{
        myClass: "red"
      }
    }
  }
</script>
<style>
.red{
  color: red;
}
</style>

1.2 v-bind:class绑定对象

<template>
  <div :class="{'active': isActive,'red': isRed}">使用对象方式绑定多个动态类名</div>
  <div class="blue" :class="{'active': isActive}">使用对象方式绑定多个动态类名</div>
</template>
<script>
  export default{
    data(){
      return{
        isActive: true,
        isRed: true
      }
    }
  }
</script>
<style>
.active{
  font-size: 30px;
  background: purple;
}
.red{
  color: red;
}
.blue{
  color: blue;
}
</style>

1.3 v-bind:class绑定数组

<template>
  <div :class=[activeClass,baseClass]>使用数组方式绑定多个类名</div>
</template>
<script>
  export default{
    data(){
      return{
        activeClass: 'active',
        baseClass: 'base'
      }
    }
  }
</script>
<style>
.base{
  width: 300px;
  height: 300px;
}
.active{
  font-size: 30px;
  background: purple;
}
</style>

1.4 数组语法 结合三目运算

<template>
  <div :class=[flag?activeClass:errorClass]>三目运算绑定class</div>
</template>
<script>
  export default{
    data(){
      return{
        flag: true,
        activeClass: 'active',
        errorClass: 'error'
      }
    }
  }
</script>
<style>
.active{
  font-size: 30px;
  background: purple;
}
.error{
  font-size: 16px;
  color: red;
}
</style>

2. 样式的绑定

2.1 使用变量绑定

<template>
  <div :style="{'color': activeColor,'fontSize': fontSize}">绑定多个style</div>
</template>
<script>
  export default{
    data(){
      return{
        activeColor:'blue',
        fontSize: '20px'
      }
    }
  }
</script>

2.2 使用对象绑定

<template>
  <div :style="styleObject">绑定多个style</div>
</template>
<script>
  export default{
    data(){
      return{
        styleObject: {
          color:'blue',
          fontSize: '40px',
        }
      }
    }
  }
</script>

2.3 使用数组方式绑定

<template>
  <div :style="[baseStyle,blueStyle]">使用数组方式绑定多个style</div>
</template>
<script>
  export default{
    data(){
      return{
        baseStyle:{
          width: '200px',
          height: '200px',  
          fontSize: '20px'
        },
        blueStyle:{
          background: "blue"
        }
      
      }
    }
  }
</script>

五:事件方法详解

1. 监听事件:v-on:click或者@click

2. 方法传值

<template>
  <h1>{{msg}}</h1>
  <button @click="setMsg('我是设置的msg')">设置msg</button>
</template>
<script>
  export default{
    data(){
      return{
        msg: '你好vue'
      }
    },
    methods:{
      setMsg(data){
        this.msg = data;
      }
    }
  }
</script>

3. 事件对象

$event传参数。

e.srcElement:当前dom节点。

e.srcElement.dataset.xxx:当前dom节点的自定义属性;

3.1 单个参数

<template>
  <button data-id="123" @click="eventFn($event)">事件对象</button>
</template>
<script>
  export default{
    data(){
      return{}
    },
    methods:{
      eventFn(e){
        e.srcElement.style.background = 'red';
        console.log(e.srcElement.dataset.id); //123
      }
    }
  }

</script>

3.2 多个参数

<template>
  <button @click="warn('传入的参数',$event)">事件对象多个参数</button>
</template>
<script>
  export default{
    data(){
      return{}
    },
    methods:{
      warn(msg, event){
        if(event){
          event.preventDefault()
        }
        alert(msg); //传入的参数
      }
    }
  }
</script>

4. 多事件处理程序

<template>
  <button @click="one(),two()">多事件处理</button>
</template>
<script>
  export default{
    data(){
      return{}
    },
    methods:{
      one(){
        console.log('one')
      },
      two(){
        console.log('two')
      }
    }
  }
</script>

5. 事件修饰符

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passiv

6. 按键修饰符

  • .enter
  • .tab
  • .delete(捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

六:模版中使用js表达式、条件判断、计算属性、watch监听

1. 模版中使用js表达式

  • 加减乘除
{{number + 1}}
{{number - 1}}
{{number * 2}}
{{number / 2}}
  • 三目运算符
{{flag?" this is true ":" this is false "}}
  • 调用方法
{{message.split("").reverse().join("")}}

2. 条件判断

v-if(dom操作)

v-else

v-else-if(必须紧随v-if或v-else-if元素)

v-show(css的显示隐藏、适合频繁操作切换)

3. 计算属性

<div>{{reverseMsg}}</div>
computed:{
	reverseMsg(){
		return this.message.split("").reverse().join("")
	}
}
计算属性实现筛选功能

4. watch监听数据变化

watch:{
	fristName: function(value){
		this.fullName = value + "  " + this.lastName
	},
	lastName: function(value){
		this.fullName = this.fristName + "  " + value
	},
}

⚠️:v-for跟v-if无法一起使用,可以使用template进行包装

<template v-for="(item,index) in list" :key="index">
    <div v-if="item.isShow">内容</div>
  </template>

七:父子组件传参数、自定义组件

1. 把整个父实例传给子组件(:属性名="this"),子组件可以获取到父组件所有的数据跟方法;

父组件的代码:
<template>
    <Header :title="title" :home="this"/>
    <button @click="setTitle">修改title的值</button>
</template>
<script>
    import Header from './Header'
    export default{
        data(){
            return{
                title: '首页',
                list: [1,2,3,4,5]
            }
        },
        components: {
            Header
        },
        methods:{
            setTitle(){
                this.title = '我是修改后的标题'

            },
            run(){
                console.log('我是首页的run方法')
            }
        }
    }
</script>

子组件代码:
<template>
    <header>{{title}}</header>
    <ul>
        <li v-for="(item,index) in home.list" :key="index">{{item}}</li>
    </ul>
    <button @click="getParentFn">获取父组件的run方法</button>
</template>
<script>
    export default{
        props: ["title","home"],
        methods: {
            getParentFn(){
                this.home.run(); //'我是首页的run方法'
            }
        }
    }
</script>
<style>
    header{
        text-align: center;
        font-size: 40px;
    }
</style>

2. 单向数据流

所有的prop都使得父子prop之间形成了一个单向下行绑定;父级prop的更新会向下流动到子组件中,但反过来不行(vue会在浏览器控制台发出警告)。这样会防止子组件意外变更父组件的状态,导致应用数据流向难以理解。

3. 父组件主动获取子组件的数据和执行子组件的方法

(可以在父组件中修改子组件的值)

3.1、调用子组件的时候定义一个ref

<v-header ref="header"></v-header>

3.2、父组件主动获取子组件数据

this.$refs.header.属性

3.3、父组件主动执行子组件方法

this.$refs.header.方法

4. 子组件主动获取父组件的数据和执行父组件方法

(可以在子组件中修改父组件的值)

4.1、子组件主动获取父组件的数据

this.$parent.数据

4.2、子组件主动执行父组件方法

this.$parent.方法

5. 自定义组件事件以及验证传入参数

@emit;

emits进行传值验证。

//父组件代码:
<template>
    <Login @submit="doLogin"/>
</template>
<script>
    import Login from './Login'
    export default{
        components:{
            Login
        },
        methods: {
            doLogin(data){
                console.log('接受来自子组件的数据', data.username, data.password)
                // 打印出来的是输入的用户名,输入的密码
            }
        }
    }
</script>

//子组件代码:
<template>
    <input type="text" v-model="userInfo.username" placeholder="用户名">
    <input type="text" v-model="userInfo.password" placeholder="密码">
    <button @click="submit">提交用户数据到父组件</button>
</template>
<script>
    export default{
        emits:{
            submit:(userInfo)=>{
                if(userInfo.username!='' && userInfo.password!=''){
                    return true
                }else{
                    console.error('用户名和密码不能为空');
                    return false
                }
            }
        },
        data(){
            return{
                userInfo:{
                    username: '',
                    password: ''
                }

            }
        },
        methods:{
            submit(){
                this.$emit('submit', this.userInfo)
            }
        }
    }
</script>

6. vue3.x第三方插件mitt实现非父子组件传值

父组件广播事件使用emit、子组件监听事件on。

6.1、安装mitt模块

npm install --save mitt

6.2、新建model/event.js

import mitt from 'mitt'
const VueEvent = mitt();
export default VueEvent;

6.3、Header组件

<template>
	<button @click="sendLogin">触发登录组件的事件并传值</button>
</template>
<script>
    import VueEvent from '../model/event'
    export default{
        data(){
            return{
                msg: '我是header组件里面的msg'
            }
        },
        methods: {
            //广播组件
            sendLogin(){
                VueEvent.emit('toLogin', this.msg)
            }
        }
    }
</script>

6.4、Login组件

<script>
    import VueEvent from '../model/event'
    export default{
        mounted() {
            //监听事件
            VueEvent.on("toLogin", function(data){
                console.log(data) //我是header组件里面的msg
            })
        }
    }
</script>

7. 自定义组件通过v-model实现双向数据绑定

父组件:v-model:自定义属性 = "xxx"

子组件:props接受 @emit触发事件

//父组件代码:
<template>
    <user-name v-model:fristName="fristName" v-model:lastName="lastName"></user-name>
    <br>
    {{fristName}} -- {{lastName}} //可以根据子组件输入的名字动态变更
</template>
<script>
    import UserName from './UserName'
    export default{
        data(){
            return{
                fristName: '',
                lastName: ''
            }
        },
        components: {
            UserName
        }
    }
</script>

//子组件代码:
<template>
    <input type="text" :value="fristName" placeholder="fristName" @input="$emit('update:fristName', $event.target.value)">
    <input type="text" :value="lastName" placeholder="lastName" @input="$emit('update:lastName', $event.target.value)">
</template>
<script>
    export default{
        props: ["fristName", "lastName"]
    }
</script>

8. 非Prop的Attribute继承

一个非prop的attribute是指传向一个组件,但是该组件并没有相应的props或emits定义的attribute,常见的示例包括class、style、id属性。

8.1、当组件返回单个根节点时,非Prop Attribute将自动添加到根节点的attribute中。

如:

父组件代码:
<template>
   <v-button class="primary" data-id="1">
        确定
    </v-button>
    <v-button class="black" data-id="2">
        取消
    </v-button>
    <v-button></v-button>
</template>
<script>
    import Button from './Button'
    export default{
        components: {
            "v-button": Button
        }
    }
</script>

子组件代码:
<template>
    <button>
        <slot>Default</slot>
    </button>
</template>
<style scoped>
    .primary{
        width: 100px;
        height: 40px;
        line-height: 40px;
        background: orange;
        color: #fff;
        border: none;
    }
    .black{
        width: 100px;
        height: 40px;
        line-height: 40px;
        background: #000;
        color: #fff;
        border: none;
    }
</style>

虽然子组件中样式写了scoped,但是在父组件中绑定样式跟非Prop的Attribute(data-id)都可自动添加到根节点(button)身上。
渲染完成的结果:
<button class="primary" data-id="1"> 确定 </button>
<button class="black" data-id="2"> 取消 </button>

8.2、自定义Attribute继承

如果你不希望组件的根元素继承attribute,你可以在组件的选项中设置inheritAttrs:false。禁用attribute继承的常见情况是需要将attribute应用于根节点之外的其他元素。然后通过访问组件的$attrs来获取。

父组件代码:
<template>
	<date-picker data-time="2021-02-05"></date-picker>
</template>
<script>
    import DatePicker from './DatePicker'
    export default{
        components: {
            DatePicker
        }
    }
</script>

子组件代码:
<template>
    <div class="date-picker">
        <input type="date" v-bind="$attrs">
    </div>
</template>
<script>
    export default{
        // 禁用默认继承
        inheritAttrs: false 
    }
</script>

渲染完成的结果:
<div class="date-picker">
	<input type="date" data-time="2021-02-05">
</div>

八:vue3.x全局绑定属性

import { createApp } from 'vue'
import App from './App.vue'
import Axios from 'axios'
import baseMixin from './mixin/baseMixin'

const app = createApp(App);
app.config.globalProperties.Axios = Axios; //其他页面用this.Axios
//全局配置mixin
app.mixin(baseMixin);
app.mount('#app');

九:vue3.x Teleport

vue3.x中的组件模版属于该组件,有时候我们想把模版的内容移动到当前组件之外的DOM中,这个时候就可以使用Teleport。 表示Teleport内包含的内容显示到body中

<teleport to="body">
	内容
</teleport>

表示Teleport内包含的内容显示到app标签中

<teleport to="#app">
	内容
</teleport>

十:Composition API(组合式API)

1. ref reactive - 定义响应式数据

ref注意点:

ref 定义字符串、Number、Boolean、数组。

ref的本质其实还是reactive,系统会自动根据我们给ref传入的值转换成ref(xx) -> reactive({value:xx})

在vue中使用ref的值不用通过value获取,会自动添加.value;但在js中使用ref的值必须通过value获取;

reactive注意点:

vue2中响应式数据是通过definePropery实现的,在vue3中是通过es6的Proxy实现的;

reactive 定义对象。

如果给reactive传递了其它对象,默认情况下修改对象,界面不会自动更新。如果想更新,可以通过重新赋值的方式;

<template>
  <div>{{state.time}}</div>
  <button @click="myFn">修改</button>
</template>
<script>
  import {reactive} from 'vue';
  export default {
    setup(){
      let state = reactive({
        time: new Date()
      });

      function myFn() {
        //直接修改以前的,界面不会更新
        // state.time.setDate(state.time.getDate() + 1);

        //重新赋值,界面才会更新
        const newTime = new Date(state.time.getTime());
        newTime.setDate(state.time.getDate() + 1);
        state.time = newTime;
        console.log(state.time);
      }
      return{
        state,
        myFn
      }
    }
  }
</script>

两者区别:

如果在template里使用的是ref类型的数据,那么vue会自动添加.value;反之不会自动添加;

vue通过当前数据的__v_isRef来判断是不是ref类型;如果值为true,代表是ref类型;

自己判断的话使用isRef、isReactive方法;isRef(xxx);

两者共同点:

默认情况下,无论是通过ref还是reactive都是递归监听;(所有的变化都会封装成proxy对象)如果数据量比较大,非常消耗性能;

通过shallowReactive跟shallowRef创建非递归监听;只封装第一层为proxy对象;

2. shallowReactive、shallowRef、triggerRef

如果是通过shallowRef创建数据,那么vue监听的是.value的变化;因为shallowRef的本质是shallowRef(xx)-> shallowReactive({value:xx})第一层是value;

triggerRef可以主动触发页面更新;没有triggerReactive方法,所以如果是reactive类型的数据,是没办法主动触发页面更新的;

//shallowReactive
<template>
    <div>{{state.a}}</div>
    <div>{{state.gf.b}}</div>
    <div>{{state.gf.f.c}}</div>
    <div>{{state.gf.f.s.d}}</div>
    <button @click="myFn">修改</button>
</template>
<script>
  import {shallowReactive} from 'vue';

  export default {
    setup() {
      let state = shallowReactive({
        a: 'a',
        gf: {
          b: 'b',
          f: {
            c: 'c',
            s: {
              d: 'd'
            }
          }
        }
      });

      function myFn() {
        // state.a = '1'; //如果把这行注释掉,页面不会更新,因为第一层数据没修改,不会触发更新ui,如果不注释,整个页面都会修改成1,2,3,4;
        state.gf.b = '2';
        state.gf.f.c = '3';
        state.gf.f.s.d = '4';

        console.log(state); //只有这一层是proxy对象
        console.log(state.gf);
        console.log(state.gf.f);
        console.log(state.gf.f.s);
      }
      return {
        state,
        myFn
      }
    }
  }
</script>
//shallowRef、triggerRef
<template>
    <div>{{state.a}}</div>
    <div>{{state.gf.b}}</div>
    <div>{{state.gf.f.c}}</div>
    <div>{{state.gf.f.s.d}}</div>
    <button @click="myFn">修改</button>
</template>
<script>
  import {shallowRef, triggerRef} from 'vue';

  export default {
    setup() {
      let state = shallowRef({
        a: 'a',
        gf: {
          b: 'b',
          f: {
            c: 'c',
            s: {
              d: 'd'
            }
          }
        }
      });

      function myFn() {
        // 以下修改方式不会更新ui。因为shallowRef的第一层数据是value
        /*state.value.a = '1';
        state.value.gf.b = '2';
        state.value.gf.f.c = '3';
        state.value.gf.f.s.d = '4';*/

        //修改value的值才会触发更新ui
        /*state.value = {
          a: '1',
          gf: {
            b: '2',
            f: {
              c: '3',
              s: {
                d: '4'
              }
            }
          }
        };*/

        //只更新第四个的值触发ui 使用triggerRef主动更新
        state.value.gf.f.s.d = 4;
        triggerRef(state);

        console.log(state.value);
        console.log(state.value.gf);
        console.log(state.value.gf.f);
        console.log(state.value.gf.f.s);
      }
      return {
        state,
        myFn
      }
    }
  }
</script>

3. toRaw、markRaw

toRaw注意点:

toRaw的作用是从reactive或ref中得到原始数据;做一些不想被监听的事情(提升性能); reactive或ref每次修改都会被追踪,都会更新UI界面。但是这样非常消耗性能,所以如果我们有一些操作不需要追踪,不需要更新Ui界面,就可以通过toRaw方法拿到它的原始数据,在进行修改,这样就不会被追踪,就不会更新UI界面,性能就好了;

如果想通过toRaw拿到ref类型的原始数据,那么就要通过toRaw获取的是.value的值;

markRaw注意点:

添加不可转为响应式数据的标记;如果修改响应式数据,数据改变,视图不会发生变化;

<template>
    reactive: {{state}}
    ref: {{state2}}
    <button @click="myFn">修改</button>
</template>
<script>
  import {reactive, ref, toRaw, markRaw} from 'vue';

  export default {
    setup() {
      let obj = {name: '张三', age: 18};
      //永远不会被追踪
      obj = markRaw(obj);
      let state = reactive(obj);
      let obj2 = toRaw(state);
      console.log(obj === state);//false
      //state和obj的关系:引用关系,state的本质是一个Proxy对象,在这个Proxy对象中引用了obj;
      console.log(obj === obj2); //true


      //如果想通过toRaw拿到ref类型的原始数据,那么就要通过toRaw获取的是.value的值
      let state2 = ref(obj);
      let obj3 = toRaw(state2.value);
      console.log(obj3); //{name: "张三", age: 18}
      console.log(obj === obj3);//true

      function myFn() {
        // 如果直接修改obj,那么是无法触发界面更新的,只有通过包装之后的对象来修改,才会触发界面的更新。
        // obj.name = '李四';
        // console.log(obj); //{name: "李四", age: 18}

        //如果指定了markRaw数据可以修改但不更新ui界面,正常可以更新UI界面
        state.name = '李四';

        //修改toRaw的原始值,值可以修改,但不会更新ui,提高性能
        // obj2.name = '李四';
        // console.log(obj2);//{name: "李四", age: 18}
        console.log(state);//Proxy{name: "李四", age: 18}
      }
      return {
        state,
        state2,
        myFn
      }
    }
  }
</script>

4. toRef、toRefs - 解构响应式对象数据、customRef

toRef注意点:

toRef 是将某个对象中的某个值转化为响应式数据,其接收两个参数,第一个参数为 obj 对象;第二个参数为对象中的属性名;

应用场景:如果想让响应式数据和之前的数据关联起来,并且更新响应式数据还不想更新ui,就可以使用toRef;

跟ref的区别:

ref:复制;修改响应式数据不会影响引用的数据;数据发生改变,界面会自动更新; toRef:引用;修改响应式数据会影响引用的数据;数据发生改变,界面不会自动更新;

<template>
    {{state}}
    <button @click="myFn">修改</button>
</template>
<script>
  import {ref, toRef, toRefs} from 'vue';

  export default {
    setup() {
      let obj = {name: '张三', age: 18};
      // ref
      // let state = ref(obj.name);

      // toRef
      // let state = toRef(obj, 'name');

      // toRefs
      // toRefs(obj)->toRefs({name: '张三', age: 18})->toRef('张三')&toRef('18')
      let state = toRefs(obj);
      function myFn() {
        //通过ref创建的,不会修改原始数据,可以更新ui
        // state.value = '李四';
        // console.log(state);//RefImpl {_rawValue: "李四", _shallow: false, __v_isRef: true, _value: "李四"}
        // console.log(obj); //{name: '张三', age: 18}


        //通过toRef创建的,会修改原始数据,不会更新ui
        // state.value = '李四';
        // console.log(state);//RefImpl {_rawValue: "李四", _shallow: false, __v_isRef: true, _value: "李四"}
        // console.log(obj); //{name: '李四', age: 18}

        //通过toRefs创建 相当于创建了两次toRef
        state.name.value = '李四';
        state.age.value = 20;
        console.log(state);//{name: ObjectRefImpl, age: ObjectRefImpl}
        console.log(obj);//{name: '李四', age: 20}
      }
      return {
        state,
        myFn
      }
    }
  }
</script>

toRefs注意点:

通过...userName解构的话就失去了响应式;

使用toRefs之后可以在模版中直接使用对象的属性名,也可以相应式;

<template>
    <div>标题:{{title}}</div>
    <div>用户信息:{{userInfo.name}} --- {{userInfo.age}}</div>
    <button @click="getUserName">获取username</button>
    <button @click="setUserName">设置username</button>
    <button @click="getTitle">获取标题</button>
    <button @click="setTitle">设置标题</button>
    
    //可以双向绑定动态更改
    <input type="text" v-model="title" placeholder="标题" />
    <input type="text" v-model="userInfo.name" placeholder="用户名称" />

    // 使用toRefs之后可以直接用属性名
    <div>fullName: {{firstName}} --- {{lastName}}</div>
</template>
<script>
    import { ref, reactive, toRefs } from 'vue'
    export default {
        setup() {
            //ref 定义字符串、Number、Boolean、数组
            //reactive 定义对象
            var title = ref('我是home组件的标题');
            var userInfo = reactive({
                name: '张三',
                age: 20
            })
            var userName = reactive({
                firstName: '张',
                lastName: '三'
            })
            //获取reactive里面定义的数据
            var getUserName = () => {
                alert(userInfo.name)
            }
            var setUserName = () => {
                userInfo.name = '李四'
            }
            //获取ref里面定义的数据
            var getTitle = () => {
                alert(title.value)
            }
            var setTitle = () => {
                title.value = '修改后的标题'
            }
            return {
                title,
                userInfo,
                getUserName,
                setUserName,
                getTitle,
                setTitle,
                ...toRefs(userName)
            }
        }
    }
</script>

customRef注意点:

自定义返回一个ref对象,可以显式地控制依赖追踪和触发响应;

<template>
    {{age}}
    <button @click="myFn">修改</button>
</template>
<script>
  import {customRef} from 'vue';

  function myRef(value) {
    return customRef((track, trigger) => {
      return {
        get() {
          track();//告诉vue这个数据是需要追踪变化的
          console.log('get', value);
          return value;
        },
        set(newValue) {
          console.log('set', newValue);
          value = newValue;
          trigger();//告诉vue触发界面更新
        }
      }
    })
  }

  export default {
    setup() {
      let age = myRef(18);

      function myFn() {
        age.value += 1;
      }

      return {
        age,
        myFn
      }
    }
  }
</script>

5. ref - 获取元素

在vue2.x中我们可以通过给元素添加ref='xxx', 然后通过refs.xxx的方式来获取元素;

在vue3.0中我们可以通过ref来获取元素;但要注意生命周期;

<template>
    <div ref="box">我是div</div>
</template>
<script>
  import {ref, onMounted} from 'vue';

  export default {
    setup() {
      // 命名需要跟ref中定义的值一样
      let box = ref(null);
      onMounted(() => {
        console.log('onMounted', box.value);//onMounted 元素
      });
      console.log(box.value);//null
      return {
        box
      }
    }
  }
</script>

6. computed - 计算属性

<template>
    <input type="text" v-model="firstName" placeholder="firstName">
    <input type="text" v-model="lastName" placeholder="lastName">
    <div>fullName: {{fullName}}</div>
</template>
<script>
    import { reactive, toRefs, computed } from 'vue'
    export default {
        setup() {
            var userName = reactive({
                firstName: '',
                lastName: ''
            })
            var fullName = computed(() => {
                return userName.firstName + '---' + userName.lastName
            })
            return {
                ...toRefs(userName),
                fullName
            }
        }
    }
</script>

7. readonly、shallowReadonly、isReadonly

readonly: 创建一个只读数据,并且是递归只读;

跟const区别:

const:赋值保护,不能给变量重新赋值;可以修改对象中的属性;

readonly:属性保护,不能给属性重新赋值;

shallowReadonly:创建一个只读数据,但不是递归只读

<template>
    <input type="text" v-model="firstName" placeholder="firstName">
    <input type="text" v-model="lastName" placeholder="lastName">
    <div>fullName: {{fullName}}</div>
</template>
<script>
    import { reactive, toRefs, computed, readonly } from 'vue'
    export default {
        setup() {
            var userName = reactive({
                firstName: '',
                lastName: ''
            })
            userName = readonly(userName);
            var fullName = computed(() => {
                return userName.firstName + '---' + userName.lastName
            })
            return {
                ...toRefs(userName),
                fullName
            }
        }
    }
</script>

8. watchEffect

9. watch

区别:

    1. watchEffect无论如何都会执行一次;watch只有值改变的时候才会执行;
    1. watchEffect可以详细监听对象中具体变化; watch不能详细监听某一个变化 只能监听reactive的返回的对象;
    1. watch可以监听变化前后的值
<template>
    <div>num: {{num}}</div>
    <div>count: {{count}}</div>
    <input type="text" v-model="count" placeholder="count">
</template>
<script>
    import { ref, reactive, toRefs, watchEffect, watch } from 'vue'
    export default {
        setup() {
            let data = reactive({
                num: 1
            })
            let count = ref('');
            //无论如何都会执行一次 num=1 可以详细监听对象中具体变化
            watchEffect(() => {
                console.log(`num=${data.num}`)
            })
            // watch不能详细监听某一个变化 只能监听reactive的返回的对象
            watch(data, () => {
                console.log(`data.num=${data.num}`);
            })
            watch(count, (newVal, oldVal) => {
                console.log(newVal, oldVal)
            })
            setInterval(() => {
                data.num++;
            }, 1000);
            return {
                count,
                ...toRefs(data)
            }
        }
    }
</script>

10. 生命周期钩子

setup注意点:

setup是在beforeCreate之前运行的,所以不需要定义他们。除此之外,其他的都可以在setup中定义,定义时需要前面加on;

setup还没有执行created方法,所以在setup中,无法使用data跟methods。

setup中this是undefined。

setup函数只能是同步的,不能是异步的。

<template></template>
<script>
    import { onMounted } from 'vue'
    export default {
        setup() {
            console.log('setup');
            onMounted(()=>{
                console.log('onMounted')
            })
        },
        beforeCreate(){
            console.log('beforeCreate');
        }
    }
</script>
//setup beforeCreate onMounted

11. props

props: ["xxx"],

setup(props) {}

父组件代码:
<template>
  <count :countNum="countNum"></count>
</template>
<script>
  import count from './components/count'
  export default{
    data(){
      return {
        countNum: 10
      }
    },
    components: {
      count
    }
  }
</script>

子组件代码:

<template></template>
<script>
    export default {
        props: ["countNum"],
        setup(props) {
            console.log('props', props.countNum); //10
        }
    }
</script>

12. Provider Inject

解决多层嵌套父子组件传值问题;

父组件provide提供数据;子组件inject注入数据;

App组件 - Home组件 - Header组件

12.1、非组合式api中的写法

⚠️:父组件修改值的时候子组件没法修改;

// App.vue中的代码:
<template>
  <Header/>
  <button @click="updateTitle">改变父组件的title</button>
  {{title}} 
</template>
<script>
  import Header from './components/Header'
  export default{
    data(){
      return {
        title: 'app根组件的标题'
      }
    },
    components: {
      Header
    },
    methods: {
      updateTitle(){
        this.title = '改变后的标题'
      }
    },
    provide(){
      return{
        title: this.title
      }
    }
  }
</script>

// header.vue里面的代码:
<template>
    <h1>header组件---{{title}}</h1>
</template>
<script>
    export default{
        inject: ["title"]
    }
</script>

//点击父组件中的button时触发事件子组件中的title值没法修改 还是之前的app根组件的标题

12.2、组合式api中的写法

子组件改变数据也会影响父组件;

// App.vue里面的代码:
<template>
  <Header/>
  <button @click="updateTitle">改变父组件的title</button>
  {{title}}
</template>
<script>
  import Header from './components/Header'
  import { ref, provide } from 'vue'
  export default {
    setup() {
      let title = ref('app中的标题');
      let updateTitle = () => {
        title.value = '修改后的title'
      }
      provide('title', title);
      return {
        title,
        updateTitle
      }
    },
    components: {
      Header
    }
  }
</script>

// header.vue里面的代码:
<template>
    <h1>header组件---{{title}}</h1>
    // 子组件改变数据也会影响父组件;
    <input type="text" v-model="title" placeholder="title">
</template>
<script>
    import {inject} from 'vue'
    export default{
        setup(){
            let title = inject('title');
            return {
                title
            }
        }
    }
</script>

十:vue3.x集合ts

1. 创建项目

npm install -g @vue/cli(只需要执行一次)
vue create <project-name>
cd <project-name>
vue add typescript

2. 定义组件

2.1 将语言设置为Typescript

<script lang="ts">
........
</script>

2.2 需要使用 defineComponent 全局方法定义组件

import { defineComponent } from 'vue';
export default defineComponent({
	//已启用类型推断
});   

示例代码:

<template>
    <div>
        Home组件 - {{title}} //这是文章标题
    </div>
</template>

<script lang="ts">
    interface Article{
        title: string,
        desc: string,
        count?:number
    }
    let article: Article = {
        title: '这是文章标题',
        desc: '这是文章描述'
    };
    import {defineComponent} from 'vue';
    export default defineComponent({
        name: 'Home',
        data() {
            return article
        }
    });
</script>

3. 与组合式API一起使用

reactive数据三种定义接口的办法:

let user:User = reactive({});

let user = reactive<User>({});

let user = reactive({}) as User;

ref数据只能用ref<T>来定义泛型;

<template>
    <div>
        User组件
        userName = {{username}}
        age = {{age}}
        <button @click="setUserName('李四')">设置name</button>
        获取name:{{getUserName()}}

        count={{count}}

        计算属性: {{reserveUserName}}
        <!-- num={{num}} -->
    </div>
</template>

<script lang="ts">
    interface User {
        username: string,
        age: number,
        setUserName(username: string): void,
        getUserName(): string
    }

    import { defineComponent, reactive, toRefs , ref, computed} from 'vue';
    export default defineComponent({
        setup() {
            //1. 实现接口的第一种写法
            let user: User = reactive({
                username: '张三',
                age: 20,
                setUserName(username: string): void {
                    this.username = username
                },
                getUserName(): string {
                    return this.username
                }
            })
            //2. 实现接口的第二种写法
            // let user = reactive<User>({
            //     username: '张三',
            //     age: 20,
            //     setUserName(username: string):void{
            //         this.username = username
            //     },
            //     getUserName():string{
            //         return this.username
            //     }
            // })
            //3. 实现接口的第三种写法
            // let user = reactive({
            //     username: '张三',
            //     age: 20,
            //     setUserName(username: string):void{
            //         this.username = username
            //     },
            //     getUserName():string{
            //         return this.username
            //     }
            // }) as User;

            let count = ref<string | number>(111);
            // let num:string = ref('111');//错误写法  
            
            let reserveUserName = computed(():string=>{
                return user.username.split('').reverse().join('')
            })
            return {
                ...toRefs(user),
                count,
                reserveUserName
                // num
            }
        }
    });
</script>

十一:vue3.x路由

一: 路由基本配置

1. 安装路由模块

npm install vue-router@next --save

2. 新建所需组件

3. 配置路由

新建src/routes.ts配置路由

import { createRouter, createWebHashHistory } from 'vue-router'
// 引入组件
import Home from './components/Home.vue'
import News from './components/News.vue'
import User from './components/User.vue'

// 配置路由
const router = createRouter({
    // hash 模式
    history: createWebHashHistory(),
    routes: [
        { path: '/', component: Home },
        { path: '/news', component: News },
        { path: '/user', component: User },
    ]
})

export default router;

4. 挂载路由

在main.ts中挂载路由

import { createApp } from 'vue'
import App from './App.vue'
import route from './routes'

let app = createApp(App);
//挂载路由
app.use(route);
app.mount('#app')

5. 渲染组件

通过router-view渲染组件

<template>
  <ul>
    <li>
      <router-link to="/">首页</router-link>
    </li>
    <li>
      <router-link to="/news">新闻</router-link>
    </li>
    <li>
      <router-link to="/user">用户</router-link>
    </li>
  </ul>
  <router-view></router-view>
</template>

<script lang="ts">
 	import { defineComponent } from 'vue';
	export default defineComponent({});
</script>

二:vue3.x 动态路由

1. 配置路由

const router = createRouter({
    history: createWebHashHistory(),
    routes: [
        { path: '/', component: Home },
        { path: '/news', component: News },
        //动态路由
        { path: '/NewsContent/:id', component: NewsContent }
    ]
})

2. 路由跳转

<ul>
      <li v-for="(item,index) in list" :key="index">
            <router-link :to="`/NewsContent/${index}`">{{item}}</router-link>
     </li>
</ul>               

3. 获取路由

this.$route.params

三:vue3.x Get传值

<router-link to="/NewsContent?id=1">get传值</router-link>
this.$route.query

四:vue3.x路由编程式导航(JS跳转路由)

1. 静态路由

 this.$router.push('/news')
 this.$router.push({
      path: '/news'
 })

2. 动态路由

this.$router.push('/NewsContent/1')
this.$router.push({
      path: '/NewsContent/1'
})
错误写法❌:
this.$router.push({
      path: '/NewsContent',
      params: {
           id: 1
     }
})

3. Get传值

this.$router.push('/NewsContent?id=1')
this.$router.push({
       path: '/NewsContent',
       query: {
          id: 1
       }
})
错误写法❌:
this.$router.push({
       path: '/NewsContent?id=1'
 })

五:vue3.x路由模式

1. hash模式(#)

import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
    // hash 模式
    history: createWebHashHistory(),
    routes: [
        ......
    ]
})

http://0.0.0.0:8080/#/news
http://0.0.0.0:8080/#/user

如果想把上面的路由改成下面的方式:
http://0.0.0.0:8080/news
http://0.0.0.0:8080/user

我们就可以使用HTML5 history 模式;

2. history模式

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
    // history 模式
    history: createWebHistory(),
    routes: [
       .......
    ]
})

注意⚠️:开启history模式后,发布到服务器需要配置伪静态;

六:vue3.x命名路由

1. 配置命名路由(name)

const router = createRouter({
    history: createWebHistory(),
    routes: [
        { path: '/NewsContent/:id', component: NewsContent, name: 'NewsContent' },
    ]
})

2. 跳转方式

2.1 router-link跳转

<router-link :to="{name:'NewsContent', params:{id: 1}}">router-link跳转</router-link>

2.2 动态路由JS跳转

this.$router.push({
       name: 'NewsContent',
       params: {
            id: 1
       }
})

2.3 Get方式JS跳转

this.$router.push({
       name: 'NewsContent',
       query: {
            id: 1
       }
})

七:路由重定向(redirect)

重定向配置要在routes.ts中完成

routes: [
	{ path: '', redirect: '/home' },
	{ path: '/home', component: Home }
]

也可以针对命名路由:

routes: [
	{ path: '', redirect: {name: 'news'} },
	{ path: '/news', component: News, name: 'news' },
]

八:路由别名(alias)

设置别名跟设置path路径的方式相同,可以设置一个,也可以多个,也可以设置动态传参数的形式。 别名的路径跟path的路径加载相同的组件。

routes: [
	{ path: '/news', component: News, name: 'news', alias:['/n','/xw'] },
	{ path: '/NewsContent/:id', component: NewsContent, name: 'NewsContent', alias:'/nc/:id' },
	{ path: '/user', component: User, alias: '/person'},
]

九:嵌套路由

import { createRouter, createWebHistory } from 'vue-router'
// 引入组件
import User from './components/User.vue'
import UserAdd from './components/User/userAdd.vue'
import UserList from './components/User/userList.vue'

// 配置路由
const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/user', component: User, redirect: '/user/list',
            children: [
                { path: '/user/list', component: UserList },
                { path: '/user/add', component: UserAdd },
            ]
        },
    ]
})

export default router;