Vue3笔记
Vue3带来的变化:
Vue2使用 Object.defineProperty来劫持数据的getter和setter方法,这种方式存在一个缺陷就是当给对象添加或删除属性时,是无法劫持和监听的
Vue3使用proxy来实现数据的劫持
vue创建项目
// 使用vue cli创建项目首先要安装vue cli npm install @vue/cli -g // 升级vue cli npm update @vue/cli -g // 通过vue的命令创建项目 vue create 项目名称复制代码
methods方法不能采用箭头函数:
Vue源码对methods中的所有函数进行遍历,并且通过bind绑定this,而箭头函数没有this
v-bind给标签绑定class属性:
// 对象语法 // 第一个active是一个字符串可以加分号也可以不加,isActive是一个变量 <div :class="{active: isActive}"></div> // 数组语法 <div :class="['abc', isActive? 'active' : '']"></div>复制代码
v-bind动态绑定属性:
<div :[name]='value'></div> 复制代码
v-bind绑定一个对象:
// 会将info对象的所有属性添加到div上 <div v-bind='info'></div> <div :='info'></div>复制代码
v-on修饰符:
.stop 调用event.stopPropagation() 停止冒泡
.prevent 调用 event.preventDefault() 阻止默认行为
.once - 只触发一次回调。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
v-on参数传递
该方法不需要参数,那么方法后的()可以不加
如果该方法需要一个参数,但是方法后面没加()会自动把event参数传递进去
如果多个参数,同时需要event是,通过 $event传入
v-for中key的作用:
在Vue中 template -> VNode -> 真实DOM
// vNode(虚拟节点)本质是一个JavaScript对象 <div class='title' style="font-size: 30px; color: red;"> 哈哈哈 </div> // 上面的div转化为vNode const vnode = { type: 'div', props: { class: 'title', style: { 'font-size': '30px', color: 'red', } }, children: '哈哈哈' }复制代码
computed:
// 使用computed时不需要加() // 大多数情况下computed的写法 computed: { fullname() { return } } // 如果想要设置computed的值 computed: { fullname: { get() { return }, set(value) { // 这里的value就是设置的值,可以在set函数里进行操作 } } }复制代码
watch
// 不需要配置选项时的写法 watch: { message(newValue, oldValue) { // 当message发生改变时,会进入该函数 } } // 需要配置选项时的写法 watch: { info: { handler(newValue, oldValue) { // 当info发生改变时,进入该函数 }, deep: true, // 开启深度监听 immediate: true // 一开始就会执行一次 }, // 可以直接监听info对象的某一个属性 'info.name': function(newValue, oldValue) {} }复制代码
$watch
created() { this.$watch('message', (newValue, oldValue) => { }, { deep: true, immediate: true }) }复制代码
v-model
<input v-model='searchText' /> // 等价于 <input :value="searchText" @input="searchText = $event.target.value" /> // 修饰符 .lazy // v-model在进行双向绑定时,绑定的是 input 事件,该事件在每次内容输入后就将最新的值和绑定的属性进行同步,如果加上.lazy会把绑定的事件切换为 change 事件 .number // 通过v-model绑定后的值总是 string 类型,如果希望转为数字类型,使用.number修饰符 .trim // 去除空格复制代码
注册组件
// 注册全局组件,全局组件可以供所有组件使用 Vue.createApp(App) // 会返回一个app对象 const app = Vue.createApp(App) app.component('组件名', {组件对象})复制代码
父子组件通信
// 父传子通过props // 数组写法 props: ['title', 'content'] // 对象写法 props: { title: String, // 类型 require: true, // 是否为必传值 default: '哈哈哈' // 默认值,如果类型为引用类型default为一个函数 } // 注意:HTML中attribute名大小写不敏感,浏览器会将所有大写字符解释为小写字符,所以在标签中props名大写需要用kebab-case(短横线分割命名) // 子传父通过emit // 在子组件中 emits: ['add'] // 数组写法 emits: { // 采用对象写法可以对传递的参数进行验证 add: null, addTen(payload) { if (payload === 10) { return true } return false } } methods: { increment() { this.$emit('add', 传递的参数) } }复制代码
非prop的Attribute
当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits,就称为非prop的Attribute。常见的包括class,style,id
Attribute继承,当组件有单个根节点,非prop的Attribute将自动添加到根节点的Attribute
如果不希望组件的根组件继承attribute,可以在组件中设置inheritAttrs:false
可以通过 $attrs来访问所有的非prop的Attribute
<div :class='$attrs.class'></div>复制代码
Provide和Inject
// 一般用于多层嵌套的子孙组件之间通信,不能用于兄弟组件 // 提供数据的组件 // 对象写法(不能通过this获取data的数据来传递,这里的this是undefined) provide: { name: 'cf', age: 18 } // 函数写法 provide() { // Vue会自动帮我们绑定provide函数的this return { name: 'cf', age: 18, length: this.names.length // 如果修改了names后,子组件使用的length不是响应式 length: computed(() => this.names.length) // 通过computed函数可以返回一个ref对象,而且该对象是响应式的 } } // 接收数据的组件 inject: ['name', 'age', 'length']复制代码
全局事件总线mitt
// 首先安装 npm install mitt // 封装一个工具eventbus.js import mitt from 'mitt' const emitter = mitt() export default emitter // 使用该工具 import emitter from './eventBus' emitter.emit('事件名称', 参数) // 发出事件 created() { emitter.on('事件名称', (参数) => {}) emitter.on('*', (type, 参数) => { // 监听所有事件,type是类型名 }) } // 取消监听 emitter.all.clear() // 取消emitter中所有监听 function onFoo() {} emitter.on('foo', onFoo) // 监听 emitter.off('foo', onFoo) // 取消监听复制代码
插槽
<slot>默认内容</slot> // 当使用该组件且没有插入内容时,显示默认内容 // 具名插槽 // 一个不带name的slot,会带有隐含的名字default <slot name='left'></slot> // 往具名插槽插入内容时 <template v-slot:left> // v-slot: 可以简写为# // 在这里插入你想要插入的内容 </template> // 动态插槽名 <slot :name='name'></slot> // 这里的name是通过props传进来的 <template v-slot:[name]></template> // 作用域插槽 // 渲染作用域:父级模板里的所有内容都是在父级作用域中编译的; 子模板里的所有内容都是在子作用域中编译的; // 使用情况,父组件在向子组件插槽插入内容时需要用到子组件的数据 <slot :item='item' :index='index'></slot> <template v-slot:default='slotProps'> // slotProps可以随便命名 slotProps.item slotProps.index </template>复制代码
动态组件
<component :is='组件名(小写)'></component> // 动态组件传值和监听,直接在component上即可复制代码
keep-alive缓存组件
<keep-alive include='a,b'> // 这里的a,b首先匹配组件自身的name选项 <component :is='组件名(小写)'></component> </keep-alive> // exclude属性,不包含 // 对于缓存的组件,再次进入时,不会执行created和mounted等生命周期函数 // 如果需要监听何时重新进入到组件,何时离开组件 // 可以使用 activated 和 deactivated复制代码
异步组件
如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent。
// defineAsyncComponent接受两种类型的参数: // 类型一: 工厂函数,该函数需要返回一个Promise对象 // 类型二: 接收一个对象类型,对异步函数进行配置 // 类型一写法 import { defineAsyncComponent } from 'vue' // 在webpack打包过程中,import()函数导入的模块会被单独打包,而且该函数返回一个Promise对象 const AsyncHome = defineAsyncComponent(() => import('./AsyncHome.vue')) // 类型二写法 const AsyncHome = defineAsyncComponent({ loader: () => import('./AsyncHome.vue'), loadingComponent: Loading, // 加载过程中显示的组件 errorComponent: Error, // 加载失败时显示的组件 // 还有一些其他的属性 })复制代码
suspense
// suspense是一个内置全局组件,该组件有两个插槽 // default: 如果default可以显示,那么就显示default的内容 // fallback:如果default不能显示,就显示fallback插槽的内容 <suspense> <template #default>default内容</template> <template #fallback>fallback内容</template> </suspense>复制代码
$refs的使用
// 获取元素对象或者子组件实例 // 通过 $parent 和 $root 获取父元素和根元素复制代码
生命周期函数
组件的v-model
<my-cpn v-model='message' /> // 等价于 <my-cpn :model-value='message' @update:model-value='message = $event' />复制代码
mixin
// 创建一个mixin对象,该对象可以包含组件的所有属性 // 需要进行mixin的组件导入该对象 import mixinObj from '路径' export default { mixins: [mixinObj] } // mixin的合并规则 // 情况一: 如果是data函数的返回值对象,默认会合并,如果属性发生冲突,保留组件自身的数据 // 情况二:如果是生命周期函数,会被合并到数组中,都会被调用 // 情况三:值为对象类型,例如methods,components,将被合并为同一个对象,如果对象key相同,保留组件对象的值 // 全局混入 const app = createApp(App); app.mixin({mixin对象})复制代码
setup函数
setup 函数(没有this)的参数:
第一个参数 props(响应式),如果采用解构,会消除prop的响应式
第二个参数 context
context(不是响应式) 也被称为setupContext 包含三个属性
attrs: 所有非prop的attribute
slots:父组件传递过来的插槽
emit:组件内发出事件
setup函数的返回值可以在template中被使用
reactive Api
// 想为setup中定义的数据提供响应式的特性,可以使用reactive函数 // reactive函数要求传入的必须是一个对象或数组 const state = reactive({name: 'cf'}) // 使用reactive函数处理的数据再次被使用时会进行依赖收集 // 当数据发生改变,所有收集到的依赖都是进行对应的响应式操作 // 事实上,data选项也是在内部交给了reactive函数复制代码
ref Api
// ref会返回一个可变的响应式对象(ref对象),它内部的值在ref的value属性中被维护 const message = ref('hello ref') // 在template中vue会帮我们进行浅层解包,所以不需要通过ref.value方式 // 在setup函数中,它依然是一个ref的引用,所以对其进行操作时,依然需要使用ref.value // 如果将一个ref对象放到一个reactive属性中,会解包 // 在setup中使用ref <h2 ref='titleRef'></h2> setup() { const titleRef = ref(null) return { titleRef } }复制代码
readonly Api
// readonly会返回原生对象的只读代理(依然是一个proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改) // readonly常见会传入三个类型的参数 // 类型一:普通对象 类型二:reactive返回的对象 类型三:ref对象 const state = readonly(info) // 经过readonly处理后返回的对象state是不允许修改的,但是info是可以修改的,而且info改变readonly返回的对象也会发生改变复制代码
toRefs Api
// 响应式对象一旦经过解构就失去响应式特性,而toRefs处理过的响应式对象解构后任然具有响应式 const { name, age } = toRefs(state) // 这里的name 和 age 都是ref对象,修改name.value或者state.name的任意一个都会引起另一个的变化复制代码
toRef Api
// 如果只希望转化响应式对象的一个属性为ref,那么可以使用toRef方法 const name = toRef(state, 'name') // name.value 和 state.name建立了连接复制代码
unref Api
// 如果想要获取一个ref引用中的value val = isRef(val) ? val.value : val unref() 就是上面的语法糖复制代码
computed Api
// 当我们的某些属性时依赖其他状态时,我们可以使用计算属性来处理 // 如何使用computed // 方式一:接收一个getter函数,且这个getter函数有返回值,会返回一个不变的ref对象 const fullName = computed(() => firstName.value + ' ' + lastName.value) // 方式二:接收一个具有get和set的对象,返回一个可变(可读写)的ref对象 const fullName = computed({ get() { return firstName.value + ' ' + lastName.value }, set(newValue) { // newValue是新修改的值 } })复制代码
watchEffect Api
// 监听某些响应式数据的变化 // watchEffect传入的函数会被立即执行一次,并且在执行过程中收集依赖,当收集的依赖发生变化时,watchEffect传入的函数会再次执行 // watchEffect会返回一个函数,执行该函数即可停止侦听 const stopWatch = watchEffect(() => {}) // watchEffect清除副作用 // 我们给watchEffect传递的函数被回调时会获取到一个参数:onInvalidate // 当副作用即将重新执行或者侦听器被停止时会执行该函数传入的回调函数 const stopWatch = watchEffect((onInvalidate) => { console.log(...) .... onInvalidate(() => { // 副作用再次执行时,会先执行该函数 }) }) // 执行setup函数时,默认情况下会立即执行传入watchEffect的副作用函数,如果不希望立即执行,可以设置副作用函数的执行时机 watchEffect(() => { }, { flush: 'post' // 默认是pre })复制代码
watch Api
// 与watchEffect比较 // 懒执行副作用(第一次不会执行) // 能更具体的说明是那些状态变化,触发侦听器的执行 // 能访问侦听前后状态变化的值 // 侦听单个数据源有两种类型 // 类型一:一个getter函数,但是该函数必须返回一个可响应式对象 watch(() => state.name, (newValue, oldValue) => { }, { deep: true, immediate: true }) // watch选项配置 // 类型二:直接写入一个可响应式对象,reactive或者ref(比较常用的是ref) watch(name, (newValue, oldValue) => { }) // 侦听器还可以使用数组侦听多个数据源 watch([name, age], (newValue, oldValue) => { }) // 如果我们希望侦听一个数组或者对象,那么可以使用一个getter函数,并且对课响应式对象进行解构 watch(() => [...names], (newValue, oldValue) => { })复制代码
setup生命周期函数
beforeCreate和created直接写在setup函数体里面即可
provide函数
provide('提供的属性名', '提供的属性值')复制代码
inject函数
const counter = inject('要inject属性名', '默认值(可以不传)')复制代码
h函数
// h函数时一个用于创建vnode的一个函数,更准确的命名应该为createVNode函数,Vue将其简化为h函数 // h函数的使用,接收三个参数 // 第一个参数:可以是String,Object,Function类型,可以是一个html标签名,也可以是一个组件,也可以是一个函数式组件 // 第二个参数:只能是object类型,与attribute,prop,和事件相对应的对象,可选的。 // 第三个参数:可以是string,array,object类型,标签体的内容,可选的 h('div', { class: 'app' }, 'hello App') // h函数可以在render函数选项中使用,也可以在setup函数选项中使用 render() { return h('div', { class: 'app' }, 'hello App') } setup() { return h('div', { class: 'app' }, 'hello App') }复制代码
自定义指令
// 自定义局部指令:组件中通过directives选项,只能在当前组件中使用 directives: { focus: { // 指令名,在使用时 v-focus mounted(el, binding, vnode, prevNode) { } } } // 自定义全局指令:app的directive方法,可以在任意组件中使用 app.directive('focus', { mounted(el, binding, vnode, prevNode) { } }) // 如果我们自定义的指令需要接受一些参数或者修饰符时 // info是参数,aaa,bbb是修饰符 <div v-focus:info.aaa.bbb></div> // 通过指令的生命周期参数binding就能获取到参数和修饰符复制代码
指令的生命周期
teleport
// 这是vue提供的内置组件,类似于react的portals // 有两个属性 // to:指定将其中的内容移动到目标元素,可以使用选择器 // disabled:是否禁用teleport功能 <teleport > <div> cf </div> </teleport>复制代码
vue-router
Url的hash:
url的hash也就是锚点(#),本质上是改变window.location的href属性
可以通过直接赋值location.hash来改变href,但是页面不发生刷新
HTML5的history(history接口有六种模式改变url而不刷新页面):
replaceState:替换原来的路径
pushState:使用新的路径
popState:路径的回退
go:向前或向后改变路径
forward:向前改变路径
back:向后改变路径
// 安装vue-router 4.0 npm install vue-router@4 // 一般情况配置路由 // 在router/index.js 中 import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' const routes = [ { path: "/", redirect: '/home' // 重定向 }, { path: '/home', component: () => import('../pages/Home.vue') // 路由懒加载 // 路由懒加载会让组件打包时进行分包 // 可以通过魔法注释为打包后的文件起名字 // import(/* webpackChunkName: "home-chunk" */ '../pages/Home') }, { path: '/user/:id', // 动态路由 component: () => import('...') }, { path: '/:pathMatch(.*)', // Not Found 页面路由匹配 // 或者 path: '/:pathMatch(.*)*' // 一个*(不会解析 '/')和**(会解析 '/' )获得的参数不同 // 可以通过$route.params.pathMatch获取传入的参数 component: () => import('...') } ... ] const router = createRouter({ routes, history: createWebHashHistory() // 采用hash模式 history: createWebHistory() // 采用history模式 }) export default router // 在main.js 中 import router from './router' app.use(router) // 要能显示路由匹配的组件还得用 <router-view />占位 // 例如在App.vue中 <template> <router-view></router-view> </template> // router-view 的 v-slot <router-view v-slot="{ Component }" > <component :is='Component' ></component> </router-view> // <router-link></router-link>组件可以进行路径改变 <router-link to='/home'>Home</router-link> // router-link属性 // to属性:是一个字符串,也可以是一个对象, // replace属性: 如果设置,点击时调用router.replace(),而不是router.push() // active-class属性: 设置活跃route对象的组件的class名称,默认是router-link-active // exact-active-class属性: 和active-class不同的是,只有精准匹配的才会有router-link-exact-active // router-link的v-slot // 在router-link标签内可以写入标签和组件,默认会用a标签包裹写入的标签或组件,如果不想要可以在router-link标签上添加custom属性 <router-link to='/about' v-slot="props"> // props所具有的属性 { href, route, navigate, isActive, isExactActive } // href: 解析后的url // route: 解析后的规范化的route对象 // navigate: 是一个函数,调用该函数即可进行跳转 // isActive,isExactActive: 是否匹配/精准匹配 </router-link> // route对象和router对象 // 在组件中要获取路由参数,可以通过route对象获取 // 可以通过router对象对路径进行跳转(编程式导航) // 在template中 <template>{{ $route.params.参数名 }}</template> // 在optionsApi中 this.$route.params.参数名 // 获取路由参数 this.$router.push('/profile') // 通过代码完成页面跳转 this.$router.push({ // push可以传递一个对象,并且在对象中传递query参数 path: '/profile', // 但是注意传递params参数会无效 query: { name: 'cf', age: 18 } }) // 在compositionApi中,可以通过useRoute()获取route对象 // 可以通过useRouter()获取router对象 import { useRoute, useRouter } from 'vue-router' setup() { const route = useRoute() const router = useRouter() route.params.参数名 } // 动态添加路由 // 在router/index.js中 router.addRoute(路由对象) // 如果是为route添加一个children路由 router.addRoute(要添加children路由的route名称, 路由对象) // 动态删除路由 // 方式一:通过添加一个name相同的路由(name属性必须唯一),后者会将前者覆盖 // 方式二: 通过removeRoute方法,传入路由名称 router.removeRoute('about') // 方式三:通过addRoute方法的返回值回调 const removeRoute = router.addRoute('about') removeRoute() // 删除路由如果存在的话 // 其他路由方法补充 router.hasRoute() // 检查路由是否存在 router.getRoutes() // 获取一个包含所有路由记录的数组 // 路由导航守卫 // 全局的前置守卫beforeEach是在导航触发时会被回调 router.beforeEach(to, from, next) { // to: 即将进入的route对象 // from: 即将离开的route对象 // next: vue3中通过返回值控制,不推荐使用该参数 // 返回值:false取消当前导航, // 不返回或者undefined进行默认导航 // 返回一个路由地址:可以是一个string类型的路径,也可以是一个对象 }复制代码
vuex
// vuex的安装 npm install vuex@next // 创建store文件夹 // 在store文件夹下的index.html import { createStore } from 'vuex' const store = createStore({ namespaced: true, // 默认情况下模块内部的actions,mutations,getters仍然是注册在全局命名空间中,这样会使得多个模块对同一个action或mutation或getter做出响应,而设置为true就不会了 state() { return { } }, mutations: { // 必须是同步函数,不然devtools就不能追踪数据变化 increment(state, payload) { // state可以拿到store对象中state属性 // payload是commit是传递的参数 // 在vuex中改变state中的值,只能通过mutations里的方法改变 // this.$store.commit('increment', 参数) } }, getters: { total(state, getters) { // getters类似于计算属性 // getters参数可以获取getters对象的其他getter // 可以让getters中的函数返回一个函数,这样在使用时需要加上括号 // 返回一个函数的好处就是可以在调用时传递参数 } }, actions: { increment(context, payload){ // 进行异步操作,提交mutation改变state // 这个context对象和store对象类似,但不是store对象 context.commit('increment') // 在组件派发action this.$store.dispatch('increment', {参数}) // 另一种派发方式,commit也可以 this.$store.dispatch({ type: 'increment', 参数 }) // 可以返回一个Promise对象,这样组件dispatch就能知道什么时候完成 // 如果在子模块中的action想要修改或派发根组件 context.commit('事件名', 参数没有就null, { root: true }) context.dispatch('事件名', 参数没有就null, { root: true }) } }, modules: { moduleA, // moduleA和moduleB都是一个对象 moduleB, // 获取子模块的状态,store.state.a // 对于模块内部的mutations和getters属性,接收的第一个参数state是一个局部状态对象 // getters对象的属性还能接收第三个参数rootState }, }) export default store // 在main.js import store from './store' app.use(store) // 在template中使用store对象 {{$store.state.counter}} // 在optionsApi中使用store对象 this.$store.state.counter // 在compositionApi里使用store对象 import { useStore } from 'vuex' const store = useStore() // 获取store对象中多个属性 // 在optionsApi中 import { mapState, mapGetters } from 'vuex' computed: { ...mapState(['state中的属性名', '']) // 还可以传对象 { name(可以修改属性名): state => state.name } ...mapGetters(['getters中的属性名']) // 还可以传对象...mapGetters({ finalPrice: 'totalPrice' }) }, methods: { ...mapMutations([]), ...mapMutations({ 自定义名: 'increment' }) ...mapActions([]) // 和mapMutations用法相同 } // 在compositionApi中不能通过 return { ...mapstate([]) } // 因为mapState,mapGetters返回的是一个对象,该对象的每一个属性都是函数,所以不能直接使用 // 封装一个函数 import { useStore, mapState, mapGetters } from 'vuex' import { computed } from 'vue' export funcion useState(mapper) { const store = useStore() const storeState = mapState(mapper) Object.keys(storeState).beforeEach(fnKey => { storeState[fnKey] = computed(storeState[fnKey].bind({$store: store})) }) return storeState } setup() { return { ...mapMutations([]) // 正常使用即可 } } // module的辅助函数 // 如果分module // 方式一:通过完整的模块空间查找,例如 ...mapState(['moduleA/counter']) // 方式二: 第一个参数传入模块空间名称,后面写上要使用的属性 ...mapState('moduleA', []) // 方式三:通过createNamespacedHelpers生成一个模块辅助函数 const { mapState, ... } = createNamespacedHelpers('moduleA') // 正常使用mapState即可复制代码
nexttick
// 如果操作dom后想要获取dom的数值,可以在nexttick中获取 nexttick(() => { }) // 如果直接获取是获取不到的,因为vue中将界面更新,watch等等回调函数放到一个微队列中,如果直接通过代码获取,此时微队列的回调函数还未执行,界面还未刷新,所以获取不到,而nexttick的回调会放到微队列的最后一个位置,所以在nexttick的回调函数中就可以获得准确的数据复制代码
手动配置webpack处理.vue文件
vue源码打包
安装vue,目前 npm install vue@next(等到vue官方将默认版本设置为3时,直接vue就可以了)
编写vue代码
import { createApp } from 'vue' createApp({ template: '#my-app', // 如果有这个属性,需要用到vue版本是 runtime + compiler data() { return { title: '123' } } }).mount('#app')复制代码
打包后会发现界面上是没有渲染的,并且我们查看控制台会发现如下警告信息
vue打包后不同版本解析
vue(.runtime).global(.prod).js:
通过浏览器中的script标签直接引用的版本
通过CDN引入和下载的vue版本就是这个版本
会暴露一个全局的vue来使用
vue(.runtime).esm-browser(.prod).js:
用于通过原生ES模块导入使用(在浏览器中通过 ,来使用)
vue(.runtime).esm-bundler.js:
用于webpack,rollup和parcel等构建工具
构建工具中默认使用是vue.runtime.esm.bundler.js (所以上面有template的界面不能显示)
如果我们需要解析模板template,那么需要手动指定vue.esm-bundler.js
vue.cjs(.prod).js:
服务端渲染使用
通过require()在node.js中使用
runtime-only VS runtime-compiler
在vue开发中有三种方式来编写DOM元素
方式一:template模板的方式
方式二:render函数的方式,使用h函数来编写渲染的内容
方式三:通过 .vue文件中的template来编写模板
它们的模板分别是如何处理的?
方式三.vue文件中的template可以通过在vue-loader对其进行编译和处理
方式一中的的template我们必须要通过源码中的一部分代码来进行编译
方式二的h函数可以直接返回一个虚拟节点,也就是VNode节点
方式一和方式三都需要有特定代码来对其进行解析
所以,vue在让我们选择版本的时候分为运行时+编译器(runtime+compiler)vs仅运行时(runtime-only)
runtime+compiler包含了对template模板的编译代码,更加完整,但是也更大一些
runtime-only没有包含对template版本的编译代码,相对更小一些
全局标识的配置:
我们会发现控制台上还会有另一个警告
在GitHub上的文档中我们可以找到说明:
这是两个特性的标识,一个是使用vue的options,一个时production模式下是否支持devtools工具
虽然他们都有默认值,但是强烈建议我们手动对他们进行配置
webpack配置.vue文件
安装vue-loader,npm install vue-loader@next -D
配webpack的模板规则中进行配置
{
test: /.vue$/i,
loader: 'vue-loader'
}复制代码安装@vue/compiler-sfc,本质上vue-loader也是用@vue/compiler-sfc来解析.vue文件
npm install @vue/compiler-sfc -D
配置对应的vue插件
const { VueLoaderPlugin } = require('vue-loader')
new VueLoaderPlugin()
作者:一天进步一点
链接:https://juejin.cn/post/7015574967609737223