一、vue-cli升级 1、安装 `npm install -g @vue/cli
OR
yarn global add @vue/cli`
#升级 `yarn global upgrade --latest @vue/cli
OR
npm update -g @vue/cli 2、创建项目 vue create my-project
OR
vue ui`
default (babel, eslint) 默认套餐,提供 babel 和 eslint 支持。
Manually select features 自己去选择需要的功能,提供更多的特性选择。比如如果想要支持 TypeScript ,就应该选择这一项。
vue-cli 内置支持了8个功能特性,可以多选:使用方向键在特性选项之间切换,使用空格键选中当前特性,使用 a 键切换选择所有,使用 i 键翻转选项。
- TypeScript 支持使用 TypeScript 书写源码。
- Progressive Web App (PWA) Support PWA 支持。
- Router 支持 vue-router 。
- Vuex 支持 vuex 。
- CSS Pre-processors 支持 CSS 预处理器。
- Linter / Formatter 支持代码风格检查和格式化。
- Unit Testing 支持单元测试。
- E2E Testing 支持 E2E 测试。
----注 如果安装下来发现不能实现热更新,这个时候可以想想是不是用cnpm (yarn)去安装的 可以再用 npm install
`main.js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' import './router/permission.js' import 'dayjs/locale/zh-cn' import locale from 'element-plus/lib/locale/lang/zh-cn'
const app = createApp(App)
app.config.globalProperties.foo = 'bar' // 注册全局属性 // app.config.globalProperties.$axios = axios app.use(ElementPlus, { locale }) element 引入 app.use(store) app.use(router) app.mount('#app')`
**引入element ** vue add element-plus
router.js
`import { createRouter, createWebHistory } from 'vue-router'
// 代替mode: history const routerHistory = createWebHistory() const router = createRouter({ history: routerHistory, routes: routes // 路由表 })`
接下来我会从以下几个属性及常用的方法,将2.0和3.0进行一些对比:
- setup
- Props
- Emit
- Data
- Method
- LifeCycle
- Computed
- Components
setup的使用
生命周期的与vue2的不同点在:beforeCreated、created都在setup()里进行默认调用,其他的都要写在setup()里面,算做compositionAPI。
props: { name: String, num: Number }, setup (props, ctx) { // setup (props, { emit }){} props.name // 获取props的name的值 ctx.emit('add', value) // 在子组件中使用emit像父组件传递值,与vue2中的this.$emit()有较大区别 }
setup()的第一个参数是props(不可进行解构,否则会丢失双向绑定),第二个参数是context
props: 由父组件传过来的值会自动被响应式,注意不能在子组件里直接修改props的值
context: 3个值 attrs,slots,emit,(可进行解构)
Data <template> <div class="hello"> 123 </div> <div>{{name.name}}</div> </template> import {reactive} from 'vue' export default { setup(){ const name = reactive({ name:'hello 番茄' }) const tag= ref(20) return { name, tag } } }
1、reactive: toRefs
import { reactive, toRefs } from "vue"; export default { setup() { // 注意事项: reactive的对象不可以结构返回或导入, 会导致失去响应式 const obj = reactive({ name: "金毛", age: 4 }); function addObj() { obj.age++; } return { ...obj, // 这样写不好, 里面会失去响应式 obj, // 这样写那么外面就要都基于obj来调取, 类型{{obj.age}} ...toRefs(obj) // 必须是reactive生成的对象, 普通对象不可以, 他把每一项都拿出来包了一下, 我们可以这样用了 {{age}}, 放心咱们多深的obj也可以响应 } } }
** 2、ref:**
import { ref } from "vue"; export default { // 1: 这个版本基本逻辑都在setup里面完成了, 有人说把他当成2.x的data. setup() { // 2: 定义一个追踪的变量,也就是双向绑定. const n = ref(1); // 生成的n是一个对象, 这样方便vue去监控它 function addN() { n.value++; // 注意这里要使用.value的形式, 因为n是对象↑, value才是他的值 } return { n, // 返回出去页面的template才可以使用它, {{n}} 不需要.value addN } } }
之前的ref获取dom元素该怎么处理?
` <div> <div ref="content">第一步, 在dom上面定义, 他会有一个回调</div>
</div> <ul> <li>v-for 出来的ref</li> <li>可以写为表达式的形式, 可以推导出vue是如何实现的</li> <li>vue2.x的时候v-for不用这么麻烦, 直接写上去会被组装成数组</li> <li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li> </ul>`
** js**
`import { ref, onMounted, onBeforeUpdate } from "vue"; export default { setup() { // 2: 定义一个变量接收dom, 名字无所谓, 但是与dom统一的话会有很好的语义化 const content = ref(null); const items = ref([]);
// 4: 在生命周期下, 这个值已经完成了变化, 所以当然第一时间就拿到
onMounted(() => {
console.log(content.value);
console.log("li标签组", items.value);
});
// 5: 确保在每次变更之前重置引用
onBeforeUpdate(() => {
items.value = [];
});
// 3: 返出去的名称要与dom的ref相同, 这样就可以接收到dom的回调
return {
content,
items
};
} };`
生命周期 2.x与 3.0的对照
-
beforeCreate -> 使用 setup()
-
created -> 使用 setup()
-
beforeMount -> onBeforeMount
-
mounted -> onMounted
-
beforeUpdate -> onBeforeUpdate
-
updated -> onUpdated
-
beforeDestroy -> onBeforeUnmount
-
destroyed -> onUnmounted
-
errorCaptured -> onErrorCaptured
路由 `<template>
<div> {{id}} </div> </template>
<script> import { getCurrentInstance, ref } from 'vue'; import { useRouter, useRoute } from 'vue-router' export default { setup(){ const router = useRouter() const route = useRoute() router.go(-1) console.log(route.query.id) const { ctx } = getCurrentInstance() // 1. 这样也是为了去掉this // 2. 方便类型推导 console.log(ctx.$router); // push等方法 console.log(ctx.$router.currentRoute.value); // 路由实例 // 这个其实没有必要变成ref因为这个值没必要动态 // 但是他太长了, 这个真的不能忍 const id = ref(ctx.$router.currentRoute.value.query.id) // 4: 页面拦截器 ctx.$router.beforeEach((to, from,next)=>{ console.log('路由的生命周期') next() }) return { id } } } </script>`
vuex
import { useStore } from 'vuex'
const store = useStore()
// 1: 单个引入 const name = computed(() => store.state.name); // 2: 引入整个state const state = computed(() => store.state); console.log("vuex的实例", state.value); // 别忘了.value // 3: 方法其实就直接从本体上取下来了 const updateName = newName => store.commit("updateName", newName); // 4: action一个意思 const deferName = () => store.dispatch("deferName"); store.dispatch('user/setTokenAndUserInfo', { token: token, userInfo }) // 5: getter 没变化 const routes = computed(() => store.getters.routes)
composition <template>
<div> <button @click="addN1">上面的增加</button>---> {{n1}} </div> <div> <button @click="addN2">下面的增加</button>---> {{n2}} <button @click="addN210">每次n2+10</button> </div> <div> <p>组件里面也可以如此引用, 这就可以代替mixin一部分功能了</p> <button @click="addN3">n3的增加</button>---> {{n3.value}} </div> <div> <com></com> </div> </template>
<script> import { ref} from 'vue'; import n3Change from './mixin'; import com from '../components/composition.vue'; export default { components:{ com }, setup(){ // 1: setup只是一个整合函数 // 2: 甚至整个函数里面可能会没有具体的逻辑 // 3: 以此推断, ref等方式定义的变量, 会自动识别在哪个setup内部, 从而达到逻辑的复用 // 4: 由此方法可以很好的代替mixin了 // 5: 当然, 这里也可以截获数据,来做一些事情 const {n2, addN2} = n2Change(); function addN210(){ n2.value += 10 } return { ...n1Change(), ...n3Change(), n2, addN2, addN210 } } } // 甚至已经可以写在任何地方了, 响应式自由度大大提高 function n1Change(){ const n1 = ref(1); let addN1 = ()=>{ n1.value++ } return { n1, addN1 } } function n2Change(){ const n2 = ref(1); let addN2 = ()=>{ n2.value++ } return { n2, addN2 } } </script>
写在任何地方, 然后导入就成了mixin
import { reactive } from 'vue';
export default ()=> { const n3 = reactive({ name: 'mixin', value: 1 }) const addN3=()=>{ n3.value++ } return { n3, addN3 } } 插件的新思路 // 开发插件并不一定要挂载到vue的原型上 // 导致vue原型臃肿, 命名冲突等等(比如两个ui都叫 message) // 原理就是 provide 和 inject, 依赖注入.
import {provide, inject} from 'vue';
// 这里使用symbol就不会造成变量名的冲突了, 这个命名权交给用户才是真正合理的架构设计 const StoreSymbol = Symbol()
export function provideString(store){ provide(StoreSymbol, store) }
export function useString() {
const store = inject(StoreSymbol)
return store } app.vue页面统一的初始化一下 export default { setup(){ // 一些初始化'配置/操作'可以在这里进行 // 需要放在对应的根节点, 因为依赖provide 和 inject provideString({ a:'可能我是axios', b:'可能我是一个message弹框' }) } } 在需要使用的组件里面接收 <template>
<div> 插件的演示 </div> </template>
<script> import { useString } from '../插件'; export default { setup(){ const store = useString(); // 不光是拿到值, 可以由app定义什么可以被拿到 console.log('拿到值了',store) } } </script>
新观察者 <template>
<div> <button @click="addn1">n1增加--{{n1}}</button> <button @click="addn2">n2增加--{{n2}}</button> <button @click="addn3">n3增加--{{n3}}</button> </div> </template>
<script> import { watch, ref } from "vue"; export default { setup() { const n1 = ref(1); const n2 = ref(1); const n3 = ref(1); // 1: 监听一个 // 第一个参数是函数返回值, 当然也可以 直接写n1 // 如果监听的是一个对象里面的某个属性, 那就需要这种函数的写法了, 比2.x的字符串写法高明很多 watch( () => n1.value, (val, oldVal) => { console.log("新值", val); console.log("老值", oldVal); } ); // 2: 监听多个 // 数组的形式定义多个, 这就出现问题了吧, 如果我观察的对象就是个数组, 并且每一项都是一个返回值的函数, 岂不是会被他误认为是多监控的结构, 苦恼 watch( [() => n2.value, ()=>n3.value], ([val, val3],[val2, val4]) => { // val 是 n2的新值 val2是 n2的老值 // val3 是 n3的新值 val4是 n3的老值 console.log("新值 与 老值 是这种对应关系", val, val2); console.log("新值 与 老值 是这种对应关系", val3, val4); } ); function addn1() { n1.value++; } function addn2() { n2.value++; } function addn3() { n3.value++; } return { addn1, addn2, addn3, n1, n2, n3 }; } }; </script>
新计算属性 <template>
<div> <button @click="addCount">点击计算</button> <button @click="setCount(1)">点击出发set</button> <p>count--{{count}}</p> <p>count2--{{count2}}</p> <p>count3--{{count3}}</p> </div> </template>
<script> // 弄得类似react了 import { computed, ref, watchEffect } from "vue"; export default { setup() { const count = ref(1); // 1. 默认的定义方式 const count2 = computed(() => count.value * 2); console.log(count2.value); // 也要value因为可能是简单类型 // 2. getter与setter当然可以定义 const count3 = computed({ get: () => count.value * 3, set: val => { // 这里重置了count count.value = val; } }); // 3. watchEffect 更像是计算函数 // 立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数 // 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。 // Vue 的响应式系统会缓存副作用函数,并异步地刷新它, 比如同时改变了count与conut4此时watchEffect只是执行一次 // 初始化运行是在组件 mounted 之前执行的。因此,如果你希望在编写副作用函数时访问 DOM(或模板 ref),请在 onMounted 钩子中进行 // 并不是返回值, 而是监听里面所有的值, 任何有变化都会重新执行, 他应该可以玩出点东西。 const count4 = ref(1); const stop = watchEffect(() => { if (count4.value) { console.log("作为判断条件也可以根据count4的变化而重新执行"); } console.log(count.value); }); setTimeout(() => { stop(); // 停止监听 }, 10000); function addCount() { count.value++; setTimeout(() => { count4.value++; }, 1000); } // 触发setter function setCount() { count3.value = 2; } return { count, count2, addCount, count3, setCount }; } }; </script>
customRef防抖 <template>
<div> <input type="text" v-model="text" /> </div> </template>
<script> import { customRef, onUnmounted } from "vue"; export default { setup() { let timeout = null; // 并不需要响应式 const text = useDebouncedRef("hello", (time) => { // 毕竟是延时的不用担心获取不到这个值 console.log("延时执行回调", text.value); console.log('时间实例', time) timeout = time; }); // 好习惯也是成为合格工程师的必要条件 onUnmounted(()=>{ clearTimeout(timeout); }) return { text }; } }; // 并不用纯粹的js编写, 可以利用customRef来监控这个值的一举一动 // 写法一般, 但是思路又多了一条, 感谢 function useDebouncedRef(value, callback, delay = 200) { let timeout; // 两个参数分别是用于追踪的 track 与用于触发响应的 trigger // 这两个参数对 值的追踪 在当前并没有用,比如watchEffect的出发机制 // 不调用这两个值没问题, 但是如果写成插件的话还是要调用的, 因为别人没准在追踪这个值, // 注意: 这个函数不可以有太大的delay, 如果超过500的话就需要考虑在组件销毁时候的清除定时器, 反而逻辑加深了, 此时我们可以每次把演示器的实例拿到 return customRef((track,trigger) => { return { get() { track() return value; }, set(newValue) { clearTimeout(timeout); // callback接受的太晚了, 可以在这里用另一个函数或对象接收 timeout = setTimeout(() => { value = newValue; trigger() callback(timeout); }, delay); } }; }); } </script>
组件与注入
父级 <template>
<div> 组件: <zj :type="type" @ok="wancheng"></zj> </div> </template>
<script> import zj from "../components/子组件.vue"; import { ref } from 'vue'; import { provide } from 'vue' export default { components: { zj }, setup() { provide('name','向下传值'); // 基础值 provide('name2', ref('向下传值')); // 监控值 const type = ref('大多数'); function wancheng(msg){ console.log('子组件-->',msg) setTimeout(()=>{ type.value = 'xxxxxxx' },2000) } return { type, wancheng } } }; </script>
自组件 <template>
<div>props的属性不用setup去return --- {{type}}</div> </template>
<script> import { inject, ref } from 'vue' // 为了让 TypeScript 正确的推导类型,我们必须使用 createComponent 来定义组件: export default { props: { type: String }, // 1: props也是不可以解构的, 会失去响应式 // 2: context是上下文, 我们可以获取到slots emit 等方法 // 3: props, context 分开也是为了ts更明确的类型推导 // setup({type}){ setup(props, context) { // 1: props console.log("props", props.type); console.log("上下文", context); context.emit('ok','传递完成') // 2: 注入 console.log('inject',inject('name')); console.log('inject',inject('xxxx','我是默认值')) inject('name1', ref('默认值')) // 接收方也可以这样 } }; </script>
什么是Composition API 如果你还不知道,Vue3 Composition API 附带了一个 setup() 方法。此方法封装了我们的大多数组件代码,并处理了响应式,生命周期钩子函数等。 简而言之,Composition API使我们能够更好地将代码组织为更多的模块化功能,而不是根据属性的类型(方法,计算的数据)进行分离。
来源:oschina
链接:https://my.oschina.net/u/3608045/blog/4944231