vue

Vue3

2020-12-09  本文已影响0人  强某某

Vue3.0的优势

Vue3.0如何变快得?

Vue3.0快速上手

当然也有脚手架和webpack的方式,但是此时只使用这个 Vite打包使用的是rollup

脚手架形式

知识点补充

<template>
  <div>
    <!-- 注意此处,本来bind里面绑定的应该是变量,但是此时传递固定值则需要加'' -->
    <a v-bind:[myHref]="'https://www.baidu.com'">跳转百度</a>
    <!-- 对象也可遍历 -->
    <li v-for="(value, name, index) in myObject" :key="index">
      {{ name }}--{{ value }}---{{ index }}
    </li>
    <button @click="one($event), two($event)">多事件处理</button>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      infos: "嘿嘿",
      infos1: "哈哈",
      myHref: "href",
      // myHref:'title',
      myObject: {
        title: "ahdhasa",
        name: "zq",
      },
    };
  },
  methods: {
    one(e) {
      console.log(e);
    },
    two(e) {
      console.log(e);
    },
  },
};
</script>

自定义事件

<template>
  <div>
    <!-- 自定义事件 -->
    <Child @send="getChild" />
  </div>
</template>
<script>
import Child from "./components/Child";
export default {
  name: "App",
  components: {
    Child
  },
  methods: {
    getChild(data) {
      console.log("子组件触发了该方法", data);
    }
  },
};
</script>
<template>
  <div>
      <p>Child</p>
      <button @click="test">执行父组件的自定义事件</button>
  </div>
</template>
<script>
export default {
    //自定义事件还可以添加上校验,但是一般不需要
    emits:{
        //这个send需要和自定义事件名称保持一致
        send:({msg})=>{
            //注意:虽然验证,但是还是会把数据传递到父组件的
            if (msg.length>10) {
                return true
            } else {
                console.log(111,"数据长度不足");
                return false
            }
        }
    },
    //建议定义的所有发出的事件都记录如下,虽然不记录也可以
    // emits:["xxx-xxx"],
    data(){
        return{
            msg:'子组件数据'
        }
    },
    methods:{
        test(){
            //send就是组件上面的自定义函数名
            // this.$emit('send','子组件数据')
           this.$emit('send',{msg:this.msg})
        }
    }
}
</script>

自定义输入框实现双向数据绑定

<template>
  <div>
    <!-- 自定义输入框组件,实现双向数据绑定,infos是自定义属性名称,不定的 -->
    <MyComponent v-model:infos="infos" v-model:infos1="infos1" />
  </div>
</template>
<script>
import MyComponent from "./components/MyComponent";
export default {
  name: "App",
  components: {
    MyComponent
  },
  data() {
    return {
      infos: "嘿嘿",
      infos1: "哈哈",
    };
  }
};
</script>
<template>
  <!-- update是固定的,infos的app.vue中的关键字是自定义的,对应起来就行 -->
  <input
    type="text"
    :value="infos"
    @input="$emit('update:infos', $event.target.value)"
  />
  <input
    type="text"
    :value="infos1"
    @input="$emit('update:infos1', $event.target.value)"
  />
</template>

<script>
export default {
  props: ["infos","infos1"],
};
</script>

非prop的attribute继承

<template>
  <div>
    <!-- 非prop的attribute继承 -->
    <Date class="datecss" />
  </div>
</template>
<template>
<!-- 其实可以发现,此处div被添加了datecss的样式 -->
  <div>
      hello world
  </div>
</template>

自定义attribute继承

<template>
  <div>
    <!-- 自定义attribute继承 -->
    <DatePicker data-time="2020-11-11" />
  </div>
</template>
<template>
<!-- 而且vue3允许多个根节点,即使默认继承如果出现多个根节点也会报警告,所以多个根节点也需要指定哪个继承,例如 v-bind="$attrs" -->
  <!-- 本来data数据属于attribute,默认是给了当面的最外层div,可以通过查看dom确定 -->
  <div class="datapicker">
      <!-- 但是此时需要让input使用,而不是上层的div -->
      <!-- 通过v-bind="$attrs"实现,前提是inhertAttrs:false,//禁用默认继承 -->
    <input type="date" v-bind="$attrs" />
  </div>
</template>

<script>
export default {
    inhertAttrs:false,//禁用默认继承
};
</script>

自定义dialog内部关闭dialog

<template>
  <div>
    <button @click="isVisible=true">弹出弹窗</button>
    <Dialog :visible="isVisible" @close-modal="isVisible=false"/>
    <Home3/>
  </div>
</template>
<script>
import Dialog from "./components/Dialog";
export default {
  name: "App",
  components: {
    Dialog
  },
  data() {
    return {
      isVisible:false
    };
  }
};
</script>
<template>
  <teleport to="body">
    <div v-if="visible">
      我是弹窗,样式不计,只说明功能
      <button @click="$emit('close-modal')">关闭</button>
    </div>
  </teleport>
</template>

<script>
export default {
  props: ["visible"],
};
</script>

teleport

<teleport to="body">
内容
</teleport>
同理可以 to="#app"这种形式

例如:弹窗组件,点击弹出,内部一定通过css样式确定该组件的位置,但是弹窗组件所有位置,可能外部有定位样式,影响了本身弹窗的样式,
导致显示混乱,这时候就可以通过teleport然后指定显示插入的节点位置,

例如放到body节点,则基本上无影响了。虽然代码形式上可能弹窗再某个组件内
部,但是实际显示时候却是再body根节点上面

vue3生命周期

a.png
import {onMounted,oUnmounted} from 'vue';
set(){
    onMounted(()=>{
        console.log('onMounted');
    });
}

Compositon API介绍

reactive

作用:创建响应式对象,非包装对象,可以认识是模板中的状态

<div>
    {{countobj.count}}-<button @click="add()">Add</button>
</div>
setup(){
    const countobj =reactive({count:0});
    const add=() => {
        countobj.count++;
    }
    return {countobj,add}
}   
<template>
  <div>child-hooks-{{ mytitle }}-{{ mytext }}</div>
  <div>
    navbar-<button @click="handleClick">navbar-click</button>
  </div>
</template>
<script>
import { ref } from "vue";
export default {
  props: ["mytitle"],
  //setup内部没有this,可以这样获取prosp;
  setup(props, { emit }) {
    // console.log(props.mytitle)
    const mytext = ref(props.mytitle + "11111111111111111");
     const handleClick = () => {
      emit('kerwinevent')
    }
    return {
      mytext,
      handleClick
    };
  },
};
</script>
<template>
  <div>
    <navbar @kerwinevent="handleChange"/>
    <sidebar v-if="state.isShow"/>
  </div>
</template>

ref

作用:创建一个包装式对象,含有一个响应式属性value。它和reactive的差别,就说前者没有包装属性value

const count =ref(0); 可以接收普通数据类型,count.value++

<div>
    {{count}}-<button @click="add()">Add</button>
</div>
setup(){
    const count =ref(0); 
    const add=() => {
        count.count++;
    }
    return {count,add}
}  

ref嵌套在reactive中

<template>
    <div class="home">
        home-{{count}}--{{state.count}}
        <button @click="add">click</button>
    </div>
</template>
<script>
import {reactive,ref} from 'vue';
export default{
    name:'Home',
    setup(){
        const count = ref(0);
        const state = reactive({count})
        const add=() => {
            state.count++
            //state.count跟ref  count都会更新;
        }
        return{ count,add,state}
    }
}
</script>

toRefs

默认直接展开state,那么此时reactive数据变成普通数据,通过toRefs,可以把reactive里的每个属性,转化为ref对象,这样展开后,就会变成多个ref对象,以然具有响应式特性

<template>
    <div class="home">
        home-{{count}}
        <button @click="add">click</button>
    </div>
</template>
<script>
import {reactive,ref} from 'vue';
export default{
    name:'Home',
    setup(){
        const state = reactive({count})
        const add=() => {
            state.count++
        }
        return{ add,...toRefs(state)}
    }
}
</script>

ref访问dom或者组件

<input type="text" ref="myinput">
const myinput =ref(null);
console.log(myinput.value.value);

计算属性

set(){
    const mytext = ref('');
    const computedSum=computed(()=>mytext.value.substring(0,1).toUpperCase()+mytext.value.substring(1)+mytext.value.substring(1));
    return {mytext,computedSum}
}

watch

监听器watch是一个方法,它包含两个参数

const reactivedata=reactive({count:1});
const text=ref("");
watch(()=>reactivedata.count,val=>{

});
watch(text,val=>{

})
//注意:reactive和ref的监听有细微区别

第一个参数是监听的值,count.value表示当count.value发生变化就会触发监听器的回调函数,即第二个参数,底二个参数可以执行监听时候的回调

vue3组合api的使用

通俗来说,option API在中小型项目没问题,但是大型项目,建议使用composition API,方便逻辑复用

<template>
  <div>
    <!-- vue3组合api的使用 -->
    <Home3/>
  </div>
</template>
<template>
  <div>------{{ title }}----------</div>
  <button @click="getTitle">getTitle</button>
  <button @click="getInfo">getInfo</button>
  <button @click="setTitle">setTitle</button>
  <p>我是分割线----------------</p>
  {{ descrip }}--{{ auth }}

  <p>{{ fullName }}</p>
  <p>watchEffect功能演示</p>
  <p>{{ data.num }}</p>
</template>

<script>
import { ref, reactive, toRefs, computed, watchEffect } from "vue";
export default {
  setup() {
    //定义响应式数据-如果不用这些定义,则只是普通数据,非响应式的(原始对象)
    //ref:基本数据类型  reactive:对象
    let title = ref("我是一个标题");
    let info = reactive({ info: "infossss" });
    let article = reactive({ descrip: "描述", auth: "作者" });
    // title=readonly(title);  //只读
    let getTitle = () => {
      console.log(title.value); //注意此处,是title.value
    };
    let setTitle = () => {
      title.value = "修改的";
    };
    let getInfo = () => {
      console.log(info.info);
    };

    const user = reactive({
      fn: "1",
      ln: "2",
    });
    //计算属性
    const fullName = computed(() => {
      return user.fn + "------" + user.ln;
    });

    //watchEffect功能
    let data = reactive({
      num: 1,
    });
    watchEffect(() => {
      //和watch不同,不论改不改变监听得值,都会执行该函数,即最开始时候也会执行一次的,也算作更新
      //而且watch必须监听data即大对象才能监听,但是watchEffect可以监听具体的data.num,
      //也就是说watchEffect可以监听当前回调里面所有数据变化,即使只是打印一个log
      console.log(`num=${data.num}`);
    });

    // watch(num1,(newValue,oldValue)=>{

    // })


    setInterval(() => {
      data.num++;
    }, 1000);

    return {
      title,
      info,
      getTitle,
      getInfo,
      setTitle,
      data,
      // ...article  //三点运算符相当于把article内部对象解构到这里,但是不是响应式数据,解决方案toRefs
      ...toRefs(article),
      fullName,
    };
  },
};
</script>

Provider Inject

通常需要将数据从父组件传递到子组件时,使用props。但是,有一些深嵌套的组件,需要来自深嵌套子组件中父组件的某些内容;这种情况,相当不方便

可以使用provide和inject对父组件可以作为其子组件的依赖项提供程序,而不管组件层次结构有多深。
这个特性有两部分,父组件provide选项来提供数据,子组件有一个inject选项来开始使用这个数据

事件总线

npm install --save mitt
import mitt from 'mitt'
const VueEvent=mitt();
export default VueEvent;

混入

hooks对应的生命周期

1.png

setup函数说明

setup函数,是在beforecreate钩子之前完成的,所以data中数据和methods中函数是无法使用的,setup中的this被vue修改成了underfined;
并且setup函数必须是同步的,不能是异步的(async)

  1. 什么是reactive?
  1. 什么是ref?
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="fn">按钮</button>
    <li v-for="stu in state.stus" :key="stu.id">{{ stu.name }}-{{ stu.id }}</li>
  </div>
</template>

<script>
import { reactive, ref } from "vue";

export default {
  name: "App",
  //setup函数是组合API得入口函数
  setup() {
    //通过这个可知,组合API内部逻辑可以是其他函数,甚至其他js文件导出的逻辑
    let { fn, count } = ctrlBtn();
    //监听复杂对象,reactive的参数必须是对象或者数组
    let state = reactive({
      stus: [
        {
          id: 1,
          name: "zq",
        },
      ],
    });
    return {
      state,
      fn,
      count,
    };
  },
};
function ctrlBtn() {
  //定义名称叫做count得变量,这个变量得初始值是0
  //ref函数只能监听简单类型的变化,不能监听复杂类型变化,例如数组,对象
  let count = ref(0);
  function fn() {
    count.value += 1;
  }
  return { count, fn };
}
</script>

监听

let state=shallowRef({
    a:'a',
    b:{
        c:'c'
    }
});
//a变化是不会更新界面的
//如下才行
state.value={....}

//triggerRef:主动更新,但是没有triggerReactive,如果是reactive类型数据是无法主动触发UI更新的
triggerRef(state);

toRaw

let state = reactive({
     name:'zq'
    });
//state其实是把传入的对象包装成了一个proxy

//获取类型的原始数据
toRaw(state);//其实就是reactive传入的对象

//ref也是可以传递对象的
let obj={name:'zq'}
let ha=ref(obj);
toRaw(ha.value);

markRaw

let obj={name:'zq'}
obj=markRaw(obj);
let state=reactive(obj);
//后面再怎么更新,UI也不会有变化

toRef

把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref,和响应式对象property--对应

let obj={name:'zq',id:1}
let state=ref(obj.name);
function myFn(){
    /*
    如果利用ref将某一个对象中的属性变成响应式数据
    如果修改响应式的数据是不会影响到原始数据的,即ref其实是复制的关系
    */
    state.value='zs';
    // obj  发现压根没变
    //state 响应式对象是变化了
}


let obj={name:'zq',id:1}
let state=toRef(obj,'name');
function myFn(){
    state.value='zs';
    // obj/state  数据都变化了

    /**
     * 利用toRef将某一个对象中的属性变成响应式的数据,修改响应式的数据是会修改原始数据的
     *  但是不会触发UI界面的更新
     * /
}

toRefs

let obj={name:'zq',id:1}
let state=toRefs(obj);
function myFn(){
    //注意此处value的位置
    state.name.value='zs';
    state.id.value=16;
}

customRef

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

 setup() {
    let age = myRef(18);
    function myFn() {
      age.value += 1;
    }
    return { age, myFn };
  },
function myRef(value) {
  return customRef((track, trigger) => {
    fetch(value)
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        value = data;
        trigger();
      })
      .catch((err) => {
        console.log(err);
      });
    return {
      get() {
        track(); //告诉vue,这个数据需要追踪变化
        //注意点:不能再get方法中发送网络请求,因为渲染界面->调用get->发送网络请求->保存数据->更新界面->调用get;形成死循环
        return value;
      },
      set(val) {
        value = val;
        trigger(); //告诉vue触发界面更新
      },
    };
  });
}

setup() {
    //使用的时候,同步使用即可
    let data = myRef("../public/data.json");
    return { data };
  },

组合API中监听生命周期

<template>
  <div>
    <button ref="btn">添加</button>
  </div>
</template>

<script>
import { onMounted, ref } from "vue";


export default {
  name: "HelloWorld",
  setup() {
    let btn=ref(null);

    //组合API中监听生命周期
    onMounted(()=>{
      console.log('onMounted',btn.value); //<button>添加</button>
    })
    return { btn };
  },
};
</script>

readonly

传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理,这个只读代理是深层的,对象内部任何嵌套的属性也都是只读的。

setup() {
    //用于创建一个只读的数据,并且是递归只读(全部只读)
    let state = readonly({ name: "zq" });
    // state.name='asfsa' //警告:Set operation on key "name" failed: target is readonly
    return { state };
  },

手写Reactive/Ref等等

function shallowReactive(obj) {
    return new Proxy(obj, {
        get(ojb, key) {
            return obj[key];
        },
        set(obj, key, val) {
            obj[key] = val;
            return true;
        }
    })
}

function shallowReadonly(obj) {
    return new Proxy(obj, {
        get(ojb, key) {
            return obj[key];
        },
        set(obj, key, val) {
            console.log(`${key}是只读的,不能赋值`);
        }
    })
}
//同理readonly其实就是把reactive的set改为输出即可

function shallowRef(val) {
    return shallowReactive({ value: val });
}
function ref(val) {
    return reactive({value:val})
}

function reactive(obj) {
    if (typeof obj === 'object') {
        if (obj instanceof Array) {
            //如果是数组,取出数组中每一个元素,判断每个元素是否是对象
            //如果又是对象也需要包装成proxy
            obj.forEach(item => {
                if (typeof item === 'object') {
                    obj[index] = reactive(item);
                }
            })
        } else {
            //如果是对象,取出对象属性的取值,判断对象属性的取值是否又是对象,如果还是对象则包装成proxy
            for (const key in obj) {
                let item = obj[key];
                if (typeof item === 'object') {
                    obj[key] = reactive(item);
                }
            }
        }
        return new Proxy(obj, {
            get(ojb, key) {
                return obj[key];
            },
            set(obj, key, val) {
                obj[key] = val;
                return true;
            }
        })
    } else {
        console.log(`${obj} is not a object`);
    }
}

TS搭建vue3.0开发环境

主要变化


<script lang="ts">
import { defineComponent } from 'vue';
import  Home  from "./components/Home.vue";
import  News  from "./components/News.vue";

export default defineComponent({
  name: 'App',
  components: {
    Home,
    News
  },
  data(){
    return{
      aaa:'ada'
    }
  }
});
</script>

基本使用

<template>
    <p>{{title}}</p>
    <button @click="setTitle">修改</button>
</template>

<script lang="ts">
import { defineComponent } from "vue";
let title:string="hahahh";
export default defineComponent({
    data(){
        return {
            title
        }
    },
    methods:{
        setTitle(){
            this.title="修改呢";
        }
    }
})
</script>

使用接口限制数据类型

<template>
<p>----{{username}}----</p>
<button @click="setUserName">改变</button>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
interface User{
    username:string,
    age:number,
    setUserName():void;
    getUserName?():void;
}
export default defineComponent({
    setup(){
        //实现接口的第一种写法
        let user:User=reactive({
            age:20,
            username:"张三",
            setUserName(){
                this.username="修改名字"
            }
           
        })
        //实现接口的第二种写法
        // let user=reactive<User>({
        //     age:20,
        //     username:"张三",
        //     setUserName(){
        //         this.username="修改名字"
        //     }
           
        // })
        //实现接口的第三种写法
        // let user=reactive({
        //     age:20,
        //     username:"张三",
        //     setUserName(){
        //         this.username="修改名字"
        //     }
           
        // }) as User

        //普通写法
        // let user=reactive({
        //     age:20,
        //     username:"张三",
        //     setUserName(){
        //         this.username="修改名字"
        //     }
        // })

        //泛型
        // let count=ref<number|string>("20");
        return{
            ...toRefs(user)
        }
    }
})
</script>

路由

npm install vue-router@next -S

import { createRouter,createWebHashHistory,createWebHistory  } from "vue-router";
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import NewsContent from "./components/NewsContent.vue";
const router=createRouter({
    history:createWebHashHistory(),//hash模式
    // history:createWebHistory(),//history模式
    routes:[
        {path:'/',component:Home},
        {path:'/news',component:News},
        {path:'/newscontent/:aid',component:NewsContent},//动态路由
    ]
})
export default router;

获取$router

import {getCurrentInstance} from 'vue'
setup(){
  //const router=useRouter();//vue-router中的useRouter直接获取router对象
  const {ctx}=getCurrentInstance();
  ctx.$router.push('/about);

  //或者直接:router.push('/about);
}

//获取动态路由参数
setup(){
  const router=useRouter();
  //或者ctx形式也行
  console.log(router.currentRoute.value.params.id);
}
import { createApp } from 'vue'
import App from './App.vue'
import route from "./routes";
const app=createApp(App);
app.use(route);
app.mount('#app');

<template>
  <router-link to="/">首页</router-link>
  <router-link to="/news">新闻</router-link>
  <router-view></router-view>
</template>
<template>
<ul>
    <li v-for="(item,index) in list" :key="index">
    <!--路由跳转-->
        <router-link :to="`/newscontent/${index}`">{{item}}</router-link>
    </li>
</ul>
</template>

<script lang="ts">
import { defineComponent} from "vue";
interface User{
    list:string[]
}
export default defineComponent({
    data(){
        return {
            list:[]
        } as User;
    },
    mounted(){
        for (let i = 0; i < 10; i++) {
            this.list.push(`我是第${i}个新闻`)
        }
    }
})
</script>
<template>
<p>NewContents组件</p>
</template>

<script lang="ts">
import { defineComponent} from "vue";

export default defineComponent({
   mounted(){
       //获取动态路由传值
       console.log(this.$route.params);
   }
})
</script>
get参数传值:http://localhost:8080/#/newscontent?aid=1230
获取: this.$route.query 
this.$router.push({path:'news'})
this.$router.push({path:'/newscontent/495'})

Vuex

setup(){
  //const store =useStore();//vuex中的useStore直接获取store对象
  const {ctx}=getCurrentInstance();
  const storeCount=computed(()=>ctx.$store.state.count);
  add(){
    ctx.$store.commit('addMutation');
  }
  return{ storeCount}
}

//store/index.js
export default Vuex.createStore({
  state:{count:1},
  mutations:{
    addMutation(state){
      state.count++;
    }
  },
  actions:{

  },
  modules:{

  }
})

不能使用mapMutations,mapState....,因为依赖于this.$store;

上一篇下一篇

猜你喜欢

热点阅读