Vue3学习笔记

304 阅读10分钟

一、创建Vue3项目的方法

1.使用vue-cli创建

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或升级vue-cli
npm i -g @vue/cli
## 创建vue3项目
vue create vue3_test

2.使用vite创建

## 创建工程
npm init vite-app <appname>
## 进入工程目录
cd <appname>
## 安装依赖
npm i
## 运行
npm run dev

二、模板语法

1.指令:

与vue2相同

2.给对象添加新的属性:

只需直接声明,无需Vue.set()

3.声明操作哪个html元素的方式与vue2不同

具体代码如下:

<body>
    <div id="div1">
        {{myname}}
        <input type="text" v-model="mytext">
        <button @click="handle">添加</button>
        <ul>
            <li v-for="(item, index) in classobj">
                {{item}}
            </li>
        </ul>
        <button @click="handleAdd">添加属性</button>
    </div>
​
    <script>
        //与vue2有差别
        
        var obj = {
            data() {
                return {
                    myname: "liumou",
                    mytext: "",
                    classobj: {
                        aa: true,
                        bb: true,
                        cc: false
                    }
                }
            },
            methods: {
                handle() {
                    console.log(this.mytext)
                },
                handleAdd() {
                    this.classobj.dd = true
                }
            },
        }
        Vue.createApp(obj).mount("#div1")
    </script>
</body>

4.列表渲染:

vm.item[indexOfItem] = newValue → 可以被页面检测

三、组件写法

1.写法一:

<body>
    <div id="div1">
        <navbar myname="aaa"></navbar>
    </div>
</body>
<script>
    var obj = {
        data() {
            return {
                myname: "kerwin"
            }
        },
        methods: {
​
        },
        computed: {
​
        }
    }
​
    Vue.createApp(obj)
        .component("navbar", {
            props: ["myname"],
            template: `
              <div>
                  navbar--{{myname}}
                  <slot></slot>   
              </div>
          `
        })
        .mount("#div1")
</script>

2.写法2:(更整洁,建议用这种)

<body>
    <div id="div1">
        <navbar myname="aaa"></navbar>
        <navbar-item></navbar-item>
    </div>
</body>
<script>
    var obj = {
        data() {
            return {
                myname: "kerwin"
            }
        },
        methods: {
​
        },
        computed: {
​
        }
    }
​
    var app = Vue.createApp(obj)
    app.component("navbar", {
        props: ["myname"],
        template: `
            <div>
                navbar--{{myname}}
                <slot></slot>   
            </div>
        `
    })
    app.component("navbar-item", {
        template: `
                <div>navbar-item</div>
            `
    })
    app.mount("#div1")
</script>

四、生命周期

1.beforeDestroy改为beforeUnmount

2.destroyed改为unmounted

3.其他的和vue2一样

五、自定义指令

1.指令生命周期(≈组件的生命周期<少一个beforeCreate>):

①created

②beforeMount

③mounted

④beforeUpdate

⑤updated

⑥beforeUnmount

⑦unmounted

六、Composition API(重点)

  • setup相当于是老式写法中的beforecreate和created

1.reactive(函数式写法--不用使用this)

(1)作用:

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

(2)注意事项:

  • vue3中 template可以放兄弟节点

    <template>
      <div>12313333</div>
      <div>232323232</div>
    </template>
    
  • reactive类似useState,如果参数是字符串、数字时,会报警告(value cannot be made reactive),所以应该设置对象,这样可以数据驱动页面

①会报错的情况

setup() {
    const obj = reactive("");
    return obj;
},

②不会报错的情况

  setup() {
    const obj = reactive({
      mytext: "",
      datalist: [],
    });
    const obj2 = reactive({
      myage: "123",
    });
    const obj3 = reactive({
      datalist2: [],
    });
    const obj4 = reactive([]);
    const handleAdd = () => {
      obj.datalist.push(obj.mytext);
      obj4.push(obj.mytext);
    };
    return {
      obj,
      obj2,
      obj3,
      obj4,
      handleAdd,
    };
  },

2.ref

(1)组件间的通信

  • ref():创建ref对象
  • .value获取dom节点
  • .value.value获取dom节点的值
<template>
  <div>
    <input type="text" ref="mytextref" />
  </div>
</template>
<script>
import { ref } from "@vue/reactivity";
export default {
  setup() {
    const mytextref = ref(); //创建ref对象
    const handleAdd = () => {
      console.log(mytextref.value); //必须是.value
    };
    return {
      mytextref,
      handleAdd,
    };
  },
};
</script>

(2)接收普通数据类型(字符串,数字)

①作用:

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

②注意事项:

  • ref可以拦截字符串(其实还是对一个对象中的.value进行拦截,只不过.value可以省略)
  • 使用ref对象中的值时,不需要使用.value
  • 修改ref对象中的值时,必须使用.value
<template>
  <div>
    ref----新写法
    <!-- 使用时不需要使用.value -->
    {{ myname }}
    <button @click="handleClick">change</button>
  </div>
</template>
<script>
import { ref } from "vue";
export default {
  setup() {
    //ref可以拦截字符串(其实还是对一个对象中的.value进行拦截,只不过.value可以省略)
    const myname = ref("kerwin");
    console.log(myname);
    const handleClick = () => {
      //修改myname中的值时,必须使用.value
      myname.value = "xiaoming";
    };
    return {
      myname,
      handleClick,
    };
  },
};
</script>

③ref实现todo功能:

<template>
  <div>
    <input type="text" ref="mytext" />

    <button @click="handleClick">add</button>

    <ul>
      <li v-for="data in datalist" :key="data">{{ data }}</li>
    </ul>
  </div>
</template>
<script>
import { ref } from "vue";
export default {
  setup() {
    const mytext = ref();
    const datalist = ref([]);//此处也可以为datalist赋初始值,如: ref(['111','222'])
    const handleClick = () => {
      datalist.value.push(mytext.value.value);
    };
    return {
      mytext,
      datalist,
      handleClick,
    };
  },
};
</script>

3.toRef - 只能导出单个属性,不能导出整个对象

(1)作用:

创建一个ref对象,其value值指向另一个对象中的某个属性

(2)格式:创建的变量name会指向person对象中的name属性(能够有响应式)

const name = toRef(person, 'name')

(3)实例:将person对象中的几个属性导出(只能分别导出)

import { toRef } from 'vue';
export default {
  setup(){
  	let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
					salary: 20,
        }
      }
    })
	},

  return {
    name: toRef(person, 'name'),
    age: toRef(person, 'age'),
    salary: toRef(person.job.j1, 'salary')
  }
}

4.toRefs

(1)作用:

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

(2)注意事项:

  • toRefs返回的是一个大对象,所以需要将大对象展开后再返回出去

toRefs返回的大对象.png

(3)代码演示:

<template>
  <div>
    {{ myname }}--{{ myage }}
    <button @click="handleClick">change</button>
  </div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
  setup() {
    //定义状态
    const obj = reactive({
      myname: "kerwin",
      myage: 100,
    });
    const handleClick = () => {
      obj.myname = "xiaoming";
      obj.myage = 200;
    };
    return {
      //toRefs返回的是一个大对象,所以需要将大对象展开来返回出去
      ...toRefs(obj),
      handleClick,
    };
  },
};
</script>

5.父子组件间通信(props,emit)

(1)注意事项:

  • props:父传子;emit:子传父
  • setup中的参数名称可以随便定义,只不过为了区分就语义化写成了props与emit
  • 子组件向父组件传递信息时,setup中的参数必须解构({emit})
  • emit (hooks) === this.$emit (类)

(2)代码

<1>父组件

<template>
  <div>
    通信
    <navbar myname="home" myid="111" @event="change"></navbar>
    <sidebar v-show="obj.isShow"></sidebar>
  </div>
</template>
<script>
import navbar from "./components/navbar";
import sidebar from "./components/sidebar";
import { reactive } from "vue";
export default {
  components: {
    navbar,
    sidebar,
  },
  setup() {
    const obj = reactive({
      isShow: true,
    });
    const change = () => {
      obj.isShow = !obj.isShow;
    };
    return {
      obj,
      change,
    };
  },
};
</script>

<2>子组件navbar----控制子组件sidebar的显示与隐藏

<template>
  <div>
    <button @click="handleShow">left</button>
    {{ myname }}---{{ myid }}
    <button>right</button>
  </div>
</template>
<script>
export default {
  props: ["myname", "myid"],
  setup(props, { emit }) {
    // console.log(props.myname,props.myid);
    const handleShow = () => {
      //emit (hooks) === this.$emit (类)
      emit("event");
    };
    return {
      handleShow,
    };
  },
};
</script>

<3>子组件sidebar

<template>
  <div>
    <ul>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
      <li>111111111111111</li>
    </ul>
  </div>
</template>

6.vue3新的生命周期

(1)生命周期

vue3新的生命周期.png

(2)代码演示

<template>
  <div>
    生命周期
    <ul>
      <li v-for="data in obj.list" :key="data">{{ data }}</li>
    </ul>
  </div>
</template>
<script>
import { reactive, onBeforeMount, onMounted } from "vue";
export default {
  setup() {
    const obj = reactive({
      list: [],
    });
    onBeforeMount(() => {
      console.log("onBeforeMount");
    });
    onMounted(() => {
      console.log("dom上树", "axios事件监听");
      setInterval(() => {
        obj.list = ["aaa", "bbb", "ccc"];
      }, 2000);
    });
    return {
      obj,
    };
  },
};
</script>

7.计算属性computed

(1)计算方法(数据每次改变都要调用方法---效率低)

<template>
  <div>
    <input type="text" v-model="obj.mytext" />
    <ul>
      <li v-for="data in filterlist()" :key="data">{{ data }}</li>
    </ul>
  </div>
</template>
<script>
import { reactive } from "vue";
export default {
  setup() {
    const obj = reactive({
      mytext: "",
      datalist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
    });
    //计算方法
    const filterlist = () => {
      return obj.datalist.filter((item) => item.includes(obj.mytext));
    };
    return {
      obj,
      filterlist,
    };
  },
};
</script>

(2)计算属性(数据有缓存,省内存)

<template>
  <div>
    <input type="text" v-model="obj.mytext" />
    <ul>
      <li v-for="data in computedList" :key="data">{{ data }}</li>
    </ul>
  </div>
</template>
<script>
import { reactive, computed } from "vue";
export default {
  setup() {
    const obj = reactive({
      mytext: "",
      datalist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
    });

    //计算属性
    const computedList = computed(() => {
      return obj.datalist.filter((item) => item.includes(obj.mytext));
    });
    return {
      obj,
      computedList,
    };
  },
};
</script>

8.watch

(1)格式:watch(监听的数据, 处理方法,监视的配置) → 第三个参数可以不写

immediate: true → 立即更新

import { watch } from 'vue';
watch(sum,(newValue,oldValue) => {
  console.log(`sum的值变化了`, newValue, oldValue);
}, {immediate: true})

(2)代码:

<template>
  <div>
    <input type="text" v-model="obj.mytext" />
    <ul>
      <li v-for="data in obj.datalist" :key="data">{{ data }}</li>
    </ul>
  </div>
</template>
<script>
import { reactive, watch } from "vue";
export default {
  setup() {
    const obj = reactive({
      mytext: "",
      datalist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
      oldlist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
    });
    watch(
      () => obj.mytext,
      () => {
        obj.datalist = obj.oldlist.filter((item) => item.includes(obj.mytext));
      }
    );
    return {
      obj,
    };
  },
};
</script>

9.watchEffect

(1)与watch的区别

watch 既要指明监视的属性,也要指明监视的回调

watchEffect 不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性

(2)watchEffect与computed的区别

①相同点

watchEffect 在监视数据改变时,监视的回调中用到哪个属性就监视哪个属性,这一点与computed相似

②不同点

computed 注重计算出来的值(回调函数的返回值),所以必须要写返回值;

watchEffect 更注重的是过程(回调函数的函数体),所以不用写返回值。

(3)实例

// watchEffect所指定的回调函数中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
  const x1 = sum.value
  const x2 = person.age
  console.log(`watchEffect配置的回调执行了`)
})

10.setup语法糖

juejin.cn/post/705253…

(1)props(子组件接收父组件传来的msg属性)

<script setup>
	const props = defineProps({
    msg: string,
  })
</script>

(2)动态组件写法component

<template>
	<component :is='num2%2==0?HelloWord:About'></component>
</template>

<script setup>
  import HelloWord from './components/HelloWord.vue';
  import About from './components/About.vue';
	import {ref} from 'vue';
  var num2 = ref(456123);
</script>

(3)emit实现父子通信

父组件中

<template>
	<HelloWord msg='hello' @sendParent='receiveEvent'></HelloWord>
	<h1>{{message}}</h1>
</template>

<script setup>
  import HelloWord from './components/HelloWord.vue';
  import About from './components/About.vue';
	import {ref} from 'vue';
  let num2 = ref(456123);
  let message = ref('');
  let receiveEvent = (event) => {
    message.value = event;
  }
</script>

子组件HelloWord中

<template>
	<h1 @click='clickEvent'>{{msg}}</h1>
</template>

<script setup>
  const props = defineProps({
    msg: string,
  })
  let clickEvent = () => {
    emit('sendParent', '这是子组件发给父组件的信息');
  }
  const emit = defineEmits(['sendParent']);
</script>

(4)defineExpose(子传父时使用)

从HelloWord中导出

<template>
	<div>helloword</div>
</template>

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

从App中引入

<template>
	<HelloWord ref='hello'></HelloWord>
</template>

<script setup>
  import HelloWord from './components/HelloWord.vue';
	import {ref} from 'vue';
  let hello = ref(null);
  const showRef = () => {
    console.log(hello.value);
  }
</script>

(5)getCurrentInstance

①作用

由于 setup 在生命周期 beforecreate 和 created 前执行,此时 vue 对象还未创建,因无法使用我们在 vue2.x 常用的 this。那就用getCurrentInstance代替this。

②使用方式(引入全局定义的axios)

main.js中通过app.config.globalProperties来挂载全局axios

import axios from 'axios';
const app = createApp(App);
app.config.globalProperties.$axios = axios;

app.use(store).mount('#app')

vue文件中通过getCurrentInstance来调用全局axios

<script setup>
	import {ref,getCurrentInstance} from 'vue';
  let hello = ref(null);
  const {proxy} = getCurrentInstance();
	proxy.$axios({
    method:'get',
    url:'xxxx',
  })
</script>

七、自定义hooks

  • !作用:将vue文件里的js操作提取出来,提高代码复用率,降低页面负担

1.app.js

import { reactive, onMounted } from 'vue'
import axios from 'axios'
function getData1() {
    const obj1 = reactive({
        list: []
    })

    onMounted(() => {
        axios.get('/test1.json').then(res => {
            obj1.list = res.data.list
        })
    })
    return obj1
}
function getData2() {
    const obj2 = reactive({
        list: []
    })

    onMounted(() => {
        axios.get('/test2.json').then(res => {
            obj2.list = res.data.list
        })
    })
    return obj2
}
export { getData1, getData2 }

2.10-app.vue

<template>
  <div>
    <ul>
      <li v-for="data in obj1.list" :key="data">{{ data.name }}</li>
    </ul>
    <ul>
      <li v-for="data in obj2.list" :key="data">{{ data.title }}</li>
    </ul>
  </div>
</template>
<script>
import { getData1, getData2 } from "./module/app";
export default {
  setup() {
    const obj1 = getData1();
    const obj2 = getData2();
    return {
      obj1,
      obj2,
    };
  },
};
</script>

八、vue-router的使用方法

1.页面跳转时的路由写法

  • router === this.$router
import { useRouter } from 'vue-router'

setup(){
	const router = useRouter()
	const handleChangePage = (id) => {
		router.push(`/detail/${id}`)
	}
	return {
		handleChangePage
	}
}

2.页面跳转后接收跳转数据

  • route === this.$route
import { useRoute } from 'vue-router'
import { onMounted } from 'vue'

setup(){
    const route = useRoute()
    onMounted(() => {
        console.log(route.params.id)
    })
}

3.router.js中创建路由

(1)路由模式

  • history模式:history: createWebHistory()
  • hash模式:history: createWebHashHistory()
const router = createRouter({
    history: createWebHistory(),	//history模式
    history: createWebHashHistory(),	//hash模式
    routes
})

(2)* 对于路由重定向--不起作用

  • 除了*,其他重定向都不变

①错误写法

{
    path: '*',
    redirect: '/films'
}

②正确写法('*' === '/' + '/:xxx' )

{
    path: '/films',
    components: Film,
    name: 'film',
    children:[]
},
    
  //路由重定向  
{
  path: '/',
  redirect: '/films'
},
{
    path: '/:kerwin',
    redirect: {
        name: '/film' //命名路由写法
    }
}

九、Vuex的使用方法

  • store === this.$store
import { useStore } from 'vuex'

setup(){
    const store = useStore()
    onMounted(() => {
        store.commit('hide')
    })
}

十、provide,inject管理公共状态(代替vuex)

  • 方法:在App.vue里将需要共享的状态用provide导出,子组件中使用inject引入共享的状态

1.代码

(1)App.vue(项目根组件)

import { provide, ref } from 'vue'

setup(){
    const isShow = ref(true)
    provide('liumoushow',isShow)
    return isShow
}

(2)子组件中

import { inject } from 'vue'

setup(){
    const isShow = inject('liumoushow')
    isShow.value = false //isShow是通过ref定义的,所以需要使用.value修改值
}

十一、其他Composition API(不常用)

1.shallowReactive与shallowRef

(1)shallowReactive:

只处理对象最外层(第一层) 属性的响应式(浅响应式)

(2)shallowRef:

只处理基本数据类型的响应式,不进行对象的响应式处理

(3)使用场景:

如果有一个对象数据,结构比较深,但变化时只是外层属性变化 → 使用 shallowReactive

如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换 → shallowRef

2.readonly与shallowReadonly

(1)readonly

让一个响应式数据变为只读的(深只读 → 整个数据都是只读)

(2)shallowReadonly

让一个响应式数据变为只读的(浅只读 → 数据的第一层为只读,更深层的可修改)

(3)应用场景

不希望数据被修改时。如:数据不是在自己组件中定义的,是其他组件给自己发来了,其他组件要求在使用时不能修改该数据,此时拿到数据是就需要在数据外套一层readonly

3.toRow与markRow

(1)toRow

①作用

将一个由reactive生成的响应式对象转为普通对象

②使用场景

用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新

(2)markRow

①作用

标记一个对象,使其永远不会再成为响应式对象

②使用场景

<1>有些值不应被设置为响应式的,例如复杂的第三方类库等

<2>当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

4.customRef

(1)作用

创建一个自定义的ref,并对其依赖项进行跟踪和更新触发进行显示控制多用于防抖效果的制作

(2)实现防抖效果代码

<template>
	<input type='text' v-model='keyword' />
  <h3>{{keyword}}</h3>
</template>

<script>
	import { customRef } from 'vue';
  export default {
    name: 'Demo',
    setup(){
      // 自定义一个myRef
      function myRef(value, delay){
        let timer
        // 通过customRef 去实现自定义
        return customRef((track, trigger) => {
          return {
            get(){
              console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
              // 通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
              track()
              return value
            },
            set(newValue){
              console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
              clearTimeout(timer)
              timer = setTimeout(() => {
                value = newValue
                // 通知Vue去重新解析模板
                trigger()
              }, delay)
            },
          }
        })
      }
      let keyword = myRef('hello',500)
      return {keyword}
    }
  }
</script>

5.响应式数据的判断

(1)isRef

检查一个值是否为一个ref对象

(2)isReactive

检查一个对象是否是由 reactive 创建的响应式代理

(3)isReadonly

检查一个对象是否是由 readonly 创建的只读代理

(4)isProxy

检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

十二、Vue3新的组件

1.Fragment - 减少标签层级,减少内存占用

在vue2中:组件必须有一个根标签

在vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中(vue3自动完成,没有代码)

2.Teleport

视频链接(可能会失效):www.bilibili.com/video/BV1Zy…

(1)作用

能够将组件的html结构移动到指定位置的技术。例如:弹窗

(2)格式

  • 移动位置可以是一个标签也可以是css的选择器(id选择器,类选择器等)
<teleport to='移动位置'>
	<div v-if='isShow' class='mask'>
    <div class='dialog'>
      <h3>我是一个弹窗</h3>
      <button onClick='isShow=false'>关闭弹窗</button>
    </div>
  </div>
</teleport>

3.Suspense

视频链接(可能会失效):www.bilibili.com/video/BV1Zy…

(1)作用:

等待异步组件(异步组件:可以将组件分离开。正常情况下,页面会等待所有组件加载完毕才会渲染,在网速较差时,会影响用户体验;采用异步组件后,就会分别渲染组件,但是如果不适用Suspense标签,会导致页面闪动)时渲染一些额外内容,让应用有更好的用户体验

(2)使用步骤

①异步引入组件

import { defineAsyncComponent } from 'vue';
const Child = defineAsyncComponent(() => import('./components/Child.vue'))

②使用 Suspense 包裹组件,并配置好 default 与 fallback

  • default:放要渲染的异步组件;
  • fallback:放异步组件渲染过程中显示的内容
<template>
	<div class='app'>
    <h3>我是App组件</h3>
    <Suspense>
  		<template v-slot:default>
				<Child />
			</template>
			<template v-slot:fallback>
				<h3>加载中......</h3>
			</template>
  	</Suspense>
  </div>
</template>

十三、pinia

官方网址:pinia.web3doc.top/

1.与Vuex的区别

①pinia 没有mutations,只有:state、getters、actions

②pinia 分模块不需要modules(之前vuex分模块需要modules)

③pinia 体积更小(性能更好)

④pinia 可以直接修改state的数据

2.pinia的配置

(1)安装下载

yarn add pinia
# or with npm
npm install pinia

(2)main.js中引入

import { createPinia } from 'pinia'

app.use(createPinia())

(3)根目录新建store/index.js中写入

import { defineStore } from 'pinia'

export const useStore = defineStore('storeId', {
  state: () => {
    return {
      counter: 0,
    }
  },
  getters:{},
  actions:{}
})

(4)组件使用

<script setup>
import { useStore } from '../store'
const store = useStore();
</script>

3.pinia的使用

xuexiluxian.cn/blog/detail…

(1)state

(2)getters

(3)actions

十四、总结(vue2和vue3区别)

1.vue3中取消了过滤器用法

2.vue3取消了中央事件bus

3.vue3底层将Object.defineProperty换成了Proxy(可以对对象中属性的添加/数组改变进行拦截)

4.vue3修改了生命周期(包括指令的生命周期)

5.vue3新增composition API,提供函数式写法