Vue3——基础内容部分(小满版本)(三)

简介: Vue3——基础内容部分(小满版本)

Vue3——基础内容部分(小满版本)(二)https://developer.aliyun.com/article/1470373

进阶 响应式原理源码实现

响应式原理


Vue2 使用的是 Object.defineProperty Vue3 使用的是 Proxy

Vue2.0的不足

核心:核心代码是使用 Object.defineProperty () 来劫持对象中每一个属性的 set 和 get 方法


总结:


缺点一:如果对象中属性过多,那么需要给每个属性都绑定 defineProperty,十分损耗效率。


缺点二:只能监听对象属性的修改、读取,如果涉及删除、增加就无法响应式刷新页面了(只能通过 Vue.set)


缺点三:通过下标、length 修改数组也不会响应式刷新页面,可以直接重新赋值,或者使用 filter、map、concat、slice 等生成新数组对其赋值

  1. 对象只能劫持 设置好的数据
  • 是否可以删除(Configurable)
  • 是否可以枚举(Enumerable)
  • 是否可以修改(Writable)
  • 返回值(value)
  • set(重要,修改触发的回调函数)
  • get(重要,获取触发的回调函数)
  1. 新增的数据需要 Vue.Set (xxx) 数组只能操作七种方法,修改某一项值无法劫持(就算能劫持也会造成性能上的问题)
  • 1.splice()2.push(),3.pop(),4.shift()5. unshift(),6.sort(),7.reverse()

Vue3 响应式

和 Vue2 不同的是,它的核心是 es6 的 Proxy 结合 Reflect 实现的,使用代理,可以不直接操作对象,这样就可以监听到所有对象的增删改查,同时也提升了效率。

手写实现reactive

实现最基础的get与set

const isObject = (target)=> target !=null && typeof target == 'object'//此行是在13分的时候加上的,准备递归,递归代码写在最下面,进行对比
export const reactive = <T extends object>(target:T)=>{//使用一个泛型约束
    return new Proxy(target,{
        get (target,key,receiver){//返回三个参数,分别为当前传入的对象、对象的属性、也是当前对象
            let res = Reflect.get(target,key,receiver)//需要使用Reflect接收三个参数,也是取值的,防止上下文错乱
            
            return res;
            //return target[key] 这种返回方式会造成上下文的错乱
        },
        set(target,key,value,receiver){//set需要接收的是一个布尔值
            let res = Reflect.set(target,key,value,receiver);//Reflect刚好返回的就是布尔值
            
            return res;
        }
    })
}
reactive({})

effect track trigger 实现

实现 effect 副作用函数


使用一个全局变量active 收集当前副作用函数,并且初始化的时候调用一下

let activeEffect:any//ts提示下需要这样定义一下,不然爆红线了
export const effect = (fn:Function)=>{//fn是匿名函数方便用户去自定义,实现依赖收集与更新
    //_effect是一个闭包,里面全局变量(全局变量在effect外面已经定义)将闭包收集起来
    const _effect = function (){
        activeEffect = _effect//收集起来
        fn()//需要调用一下去执行effect的副作用函数
    }
    
    _effect()//首次默认调用一下
}

实现 track

image.png

//target参数被当作targetMap中的key收集起来了
//Value里的new Map的key对应的就算targetMap的key。然后new Map对key去收集依赖(也就是set结构),set结构下去存effect副作用函数
----------------------------------------------------------->
//注意:entries?: [object, any][] | null): WeakMap<object, any>,也就是说weakMap只接收对象类型
const targetMap = new WeakMap()//key需要拼装格式,需要一个全局变量先存一下
export const track = (target?:any,key?:any) => {//target是一个对象
    //定义好第一层
    let depsMap = targetMap.get(target)//target是targetMap下的key,我们先获取key
    if (!depsMap){//第一次值是取不到的
        depsMap = new Map()//此时我们进入流程图的第一阶段,将new Map传入最外层的depsMap
        target.set(target,depsMap)//分别对应了new Map往下传的key与Value
    }
    //定义好第二层
    let deps = depsMap.get(key)//获取第二层的new Set值
    if (!deps){//一样的,第一次的值还是取不到的
        deps = new Set()//取不到值,但是我们获取到了第二层的new Set
        depsMap.set(key,deps)//通过new Map将key与value关联起来
    }
    deps.add(activeEffect)//将副作用函数添加进来,也就是流程图最下面的操作
}
export const trigger = (target:any,key:any)=>{
    const depsMap = target.get(target)//通过targetMap下的key取到Map下的key了
    if (depsMap){
         const deps = depsMap.get(key)//又是重复的操作,通过Map下的key取到了依赖
        //取到依赖后进行一个更新(遍历数组),调用上面定义好的副作用函数
        deps.forEach((effect:any)=>effect())
    }else{
        return "不好意思,你没取到值"
    }
}

完整形式代码

reactive有进行改动,并非单纯拼凑

let activeEffect:any
export const effect:any = (fn:Function)=>{//fn是匿名函数方便用户去自定义,实现依赖收集与更新
    //_effect是一个闭包,里面定义全局变量将闭包收集起来
    const _effect = function (){
        activeEffect = _effect//收集起来
        fn()//需要调用一下去执行effect的副作用函数
    }
    _effect()//首次默认调用一下
}
//注意:entries?: [object, any][] | null): WeakMap<object, any>,也就是说weakMap只接收对象类型
const targetMap = new WeakMap()//key需要拼装格式,需要一个全局变量先存一下
export const track = (target?:any,key?:any) => {//target是一个对象
    //定义好第一层
    let depsMap = targetMap.get(target)//target是targetMap下的key,我们先获取key
    if (!depsMap){//第一次值是取不到的
        depsMap = new Map()//此时我们进入流程图的第一阶段,将new Map传入最外层的depsMap
        target.set(target,depsMap)//分别对应了new Map往下传的key与Value
    }
    //定义好第二层
    let deps = depsMap.get(key)//获取第二层的new Set值
    if (!deps){//一样的,第一次的值还是取不到的
        deps = new Set()//取不到值,但是我们获取到了第二层的new Set
        depsMap.set(key,deps)//通过new Map将key与value关联起来
    }
    deps.add(activeEffect)//将副作用函数添加进来,也就是流程图最下面的操作
}
export const trigger = (target:any,key:any)=>{
    const depsMap = target.get(target)//通过targetMap下的key取到Map下的key了
    if (depsMap){
        const deps = depsMap.get(key)//又是重复的操作,通过Map下的key取到了依赖
        //取到依赖后进行一个更新(遍历数组),调用上面定义好的副作用函数
        deps.forEach((effect:any)=>effect())
    }else{
        return "不好意思,你没取到值"
    }
}
export const reactive = <T extends object>(target:T)=>{//使用一个泛型约束
    return new Proxy(target,{
        get (target,key,receiver){//返回三个参数,分别为当前传入的对象、对象的属性、接收方
            let res = Reflect.get(target,key,receiver)//需要使用Reflect接收三个参数,也是取值的,防止上下文错乱
            track(target,key)//传入target
            return res;
            //return target[key] 这种返回方式会造成上下文的错乱
        },
        set(target,key,value,receiver){//set需要接收的是一个布尔值
            let res = Reflect.set(target,key,value,receiver);//Reflect刚好返回的就是布尔值
            trigger(target,key)//传入trigger
            return res;
        }
    })
}
reactive({})

测试代码

html使用import注意加上type 并且要起一个服务 如liveServer

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
 
<body>
 
    <div id="app">
 
    </div>
 
    <script type="module">
        import { reactive } from './reactive.js'
        import { effect } from './effect.js'
        const user = reactive({
            name: "小满",
            age: 18
        })
        effect(() => {
            document.querySelector('#app').innerText = `${user.name} - ${user.age}`
        })
 
        setTimeout(()=>{
            user.name = '小满打出了康康卡'
            setTimeout(()=>{
                user.age = '23'
            },1000)
        },2000)
 
    </script>
</body>
 
</html>

递归实现

import { track, trigger } from './effect'
 
const isObject = (target) => target != null && typeof target == 'object'
 
export const reactive = <T extends object>(target: T) => {
    return new Proxy(target, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver) as object
 
            track(target, key)
 
            if (isObject(res)) {//深层次的劫持
                return reactive(res)
            }
 
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
 
            trigger(target, key)
 
            return res
        }
    })
}

新的测试html的demo

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
 
<body>
 
    <div id="app">
 
    </div>
 
    <script type="module">
        import { reactive } from './reactive.js'
        import { effect } from './effect.js'
        const user = reactive({
            name: "小满",
            age: 18,
            foo:{
                bar:{
                    sss:123
                }
            }
        })
        effect(() => {
            document.querySelector('#app').innerText = `${user.name} - ${user.age}-${user.foo.bar.sss}`
        })
 
        setTimeout(()=>{
            user.name = '麒麟哥的笔记是txt格式的'
            setTimeout(()=>{
                user.age = '23'
                setTimeout(()=>{
                    user.foo.bar.sss = 66666666
                },1000)
            },1000)
        },2000)
 
    </script>
</body>
 
</html>

第九章 — computed计算属性

  1. 接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。
  2. 当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。

购物车案例

<template>
   <div>
      <table style="width:800px" border>
         <thead>
            <tr>
               <th>名称</th>
               <th>数量</th>
               <th>价格</th>
               <th>操作</th>
            </tr>
         </thead>
         <tbody>
            <tr :key="index" v-for="(item, index) in data">
               <td align="center">{{ item.name }}</td>
               <td align="center">
                  <button @click="AddAnbSub(item, false)">-</button>
                  {{ item.num }}
                  <button @click="AddAnbSub(item, true)">+</button>
               </td>
               <td align="center">{{ item.num * item.price }}</td>
               <td align="center">
                  <button @click="del(index)">删除</button>
               </td>
            </tr>
         </tbody>
         <tfoot>
            <tr>
               <td></td>
               <td></td>
               <td></td>
               <td align="center">总价:{{ $total }}</td>
            </tr>
         </tfoot>
      </table>
   </div>
</template>
 
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
type Shop = {
   name: string,
   num: number,
   price: number
}
let $total = ref<number>(0)
const data = reactive<Shop[]>([
   {
      name: "小满的袜子",
      num: 1,
      price: 100
   },
   {
      name: "小满的裤子",
      num: 1,
      price: 200
   },
   {
      name: "小满的衣服",
      num: 1,
      price: 300
   },
   {
      name: "小满的毛巾",
      num: 1,
      price: 400
   }
])
 
const AddAnbSub = (item: Shop, type: boolean = false): void => {
   if (item.num > 1 && !type) {
      item.num--
   }
   if (item.num <= 99 && type) {
      item.num++
   }
}
const del = (index: number) => {
   data.splice(index, 1)
}
 
 
 
$total = computed<number>(() => {
   return data.reduce((prev, next) => {
      return prev + (next.num * next.price)
   }, 0)
})
 
 
 
</script>
 
<style>
</style>

第十章 — watch侦听器 & 源码讲解

用于声明在数据更改时调用的侦听回调。


watch 选项期望接受一个对象,其中键是需要侦听的响应式组件实例属性 (例如,通过 datacomputed 声明的属性)—— 值是相应的回调函数。该回调函数接受被侦听源的新值和旧值。


第一个参数新值,第二个参数旧值

  • 第三个参数是决定是否深层次监听的(deep:true),但其实我们通过reactive也一样可以实现深层次监听
  • 但是有一个问题,就是通过控制台我们可以看到新值旧值都已经被新值覆盖了
  • immediate决定是否一开始就自调用

侦听单个数据源

<template>
  <div>
    case1:<input v-model="message" type="text">
    <hr>
    case2:<input type="text">
  </div>
  <h1>{{tom}}</h1>
</template>
<script setup lang="ts">
import {ref} from "vue";
let message = ref<string>('小满')
//监听器第一个参数:侦听的数据源(sources)  第二个参数 回调函数 cb(newVal,oldVal)
watch(message,(newVal,oldVal)=>{
    console.log('新的值----', newVal);
    console.log('旧的值----', oldVal);
})
</script>
<style scoped>
</style>

侦听多个数据源

<template>
  <div>
    case1:<input v-model="message" type="text">
    <hr>
    case2:<input v-model="message2" type="text">
  </div>
  <h1>{{tom}}</h1>
</template>
<script setup lang="ts">
import {ref} from "vue";
let message = ref<string>('小满')
let message2 = ref<string>('喜多川学姐')
//使用数组的形式侦听多个数据源,返回的结果也会变成数组,结果顺序 按照 监听顺序
watch([message,message2],(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
    console.log('新的值----', newVal);
    console.log('旧的值----', oldVal);
})
</script>
<style scoped>
</style>

监听 Reactive

使用 reactive 监听深层对象开启和不开启 deep 效果一样


深层次监听啦啦啦,需要给reactive开启第三个属性option(是一个配置项) => 里面有一个deep就是用来代表深度监听的

//监听语句变成如下
case1:<input v-model="message.nav.bar.name" type="text">
import { ref, watch ,reactive} from 'vue'
 
let message = ref({//reactive已经隐性开启deep了,不需要再手动开启了
    nav:{
        bar:{
            name:"学姐好好吃饭"
        }
    }
})
 
watch([message,message2],(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
},{deep:true})//深度监听

image.png

通过控制台打印,我们发现了其中的Proxy下的深层次监听


此时会发现有一个问题,那就是旧值跟新值的内容是一样的(原因是因为引用类型返回的新值是跟旧值一样的)

image.png

我们在使用的时候会发现一个问题,那就是我只改变了一个值,为什么没有改变的那个值(例如上面的喜多川学姐)也跟着带出来了,我只想要得到改变的值,那要怎么办到呢?  =>  简洁版提问:想要侦听单一属性

  • Vue推荐是让我们把要监听的变成一个函数(而不是直接.xxx.xxx得到的字符串,这样会报错的,因为不是Proxy所代理的对象)
  • 创建一个回调函数去返回这个要侦听的属性

//区别就是()=>message.value.nav.bar.name跟单纯message.value.nav.bar.name,后者会报错
watch(()=>message.value.nav.bar.name,(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
},{deep:true})


immediate

watch侦听器 第三参数


开局立即执行一次

watch(()=>message.value.nav.bar.name,(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
},{immediate:true})

flush

watch侦听器 第三参数


1

watch(()=>message.value.nav.bar.name,(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
},{
    flush:"pre"//pre组件更新之前调用 sync同步执行 post组件更新之后做的执行
})

源码讲解 黄金流程

位于视频7分钟


源码位置:packages > runtime-core > src > apiWatch.ts > watch

// overload: watching reactive object w/ cb
export function watch<
  T extends object,
  Immediate extends Readonly<boolean> = false
>(
  source: T,
  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
  options?: WatchOptions<Immediate>
): WatchStopHandle
// implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
  source: T | WatchSource<T>,
  cb: any,
  options?: WatchOptions<Immediate>
): WatchStopHandle {
  if (__DEV__ && !isFunction(cb)) {
    warn(
      `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
        `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
        `supports \`watch(source, cb, options?) signature.`
    )
  }
  return doWatch(source as any, cb, options)//核心函数调用了doWatch。source数据源,cd回调函数(新旧值),option配置项
}
function doWatch(
//source支持4种模式,ref对象、reactive对象、数组侦听多个源,传入函数侦听单一属性
  source: WatchSource | WatchSource[] | WatchEffect | object,//第一件事情格式化source,将四种类型都赋给一个函数
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  if (__DEV__ && !cb) {
    if (immediate !== undefined) {
      warn(
        `watch() "immediate" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      )
    }
    if (deep !== undefined) {
      warn(
        `watch() "deep" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      )
    }
  }
const warnInvalidSource = (s: unknown) => {
    warn(
      `Invalid watch source: `,
      s,
      `A watch source can only be a getter/effect function, a ref, ` +
        `a reactive object, or an array of these types.`
    )
  }
  //ref Reactive [msg,msg2] ()=>msg.bar.name
  const instance = currentInstance
  let getter: () => any
  let forceTrigger = false
  let isMultiSource = false
//如果是ref对象
  if (isRef(source)) {
    //创建一个getter函数并且读取了 ref对象的value属性
    getter = () => source.value
    forceTrigger = isShallow(source)
  } else if (isReactive(source)) {
    //如果是Reactive 对象直接返回一个getter 函数 并且设置deep 为true,所以就是刚刚上面说reactive不需要在手动打开deep的原因,因为源码已经默认开启了
    getter = () => source
    deep = true
  } else if (isArray(source)) {
    //如果是数组 就遍历该数组 然后处理里面的ref 和 Reactive
    isMultiSource = true
    forceTrigger = source.some(s => isReactive(s) || isShallow(s))
    getter = () =>
      source.map(s => {//对数组做了一个遍历
        if (isRef(s)) {
          return s.value
        } else if (isReactive(s)) {
          return traverse(s)//traverse是一个递归,会把里面的每个属性都做一个侦听
        } else if (isFunction(s)) {
          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
          __DEV__ && warnInvalidSource(s)
        }
      })
  } else if (isFunction(source)) {
    //如果source 是一个函数 则会判断 cb是否存在 getter就会对 source进行简单的封装
    if (cb) {//判断有无cb,决定走cb还是with effect
      // getter with cb
      getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
      // no cb -> simple effect
      getter = () => {
        if (instance && instance.isUnmounted) {
          return
        }
        if (cleanup) {
          cleanup()
        }
        return callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onCleanup]
        )
      }
    }
  } else {
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }
//处理deep 深度监听
  if (cb && deep) {
    const baseGetter = getter
    getter = () => traverse(baseGetter())//traverse 一个比较耗时的递归
  }
job.allowRecurse = !!cb
//调度都赋给scheduler
  let scheduler: EffectScheduler
  if (flush === 'sync') {//如果sync就同步执行
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {//post的话又把值传给了job
    //组件更新之后执行 queuePostRenderEffect,源码中有看到这个就一定是在组件更新之后去做一个执行的
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    job.pre = true
    if (instance) job.id = instance.uid
    scheduler = () => queueJob(job)
  }
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE//初始化旧值
  const job: SchedulerJob = () => {
    if (!effect.active) {
      return
    }
      
      
      
    if (cb) {
      // watch(source, cb)
      const newValue = effect.run()
      if (
        deep ||
        forceTrigger ||
        (isMultiSource
          ? (newValue as any[]).some((v, i) =>
              hasChanged(v, (oldValue as any[])[i])
            )
          : hasChanged(newValue, oldValue)) ||
        (__COMPAT__ &&
          isArray(newValue) &&
          isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
      ) {
        // cleanup before running cb again
        if (cleanup) {
          cleanup()
        }
        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
          newValue,
          // pass undefined as the old value when it's changed for the first time
            //第一次执行旧值 是undefined 或者 空数组
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          onCleanup
        ])
        //直接赋值,如果是对象旧直接引用了,所以新值和旧值是一样的。这个就是为什么引用类型新旧值没有变化
        oldValue = newValue
      }
    } else {
      // watchEffect
      effect.run()
    }
  }

第十一章 — watchEffect高级侦听器

  • 跟watch是不一样的,watchEffect是非惰性的。函数会帮你自己去调用一下

watchEffect(一开始会自己自调用一次)

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

如果用到 message 就只会监听 message 就是用到几个监听几个 而且是非惰性 会默认调用一次

let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect(() => {
    //console.log('message', message.value);
    console.log('message2', message2.value);
})

清除副作用

就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖

import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
        
    })
    console.log('message2', message2.value);
})

停止跟踪 watchEffect 返回一个函数 调用之后将停止更新

const stop =  watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
 
    })
    console.log('message2', message2.value);
},{
    flush:"post",
    onTrigger () {
 
    }
})
stop()

在watchEffect里面会先处理回调函数,在处理其他内容,所以我们可以在里面去进行防抖之类的操作

断言 (assertion) 是一种在程序中的一阶逻辑 (如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果 —— 当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。

副作用刷新时机 flush 一般使用 post

pre

sync

post

更新时机

组件更新前执行

强制效果始终同步触发

组件更新后执行


Vue3——基础内容部分(小满版本)(四)https://developer.aliyun.com/article/1470376

目录
相关文章
|
1月前
|
JavaScript JSON 资源调度
Vue3——基础内容部分(小满版本)(一)
Vue3——基础内容部分(小满版本)
30 0
|
缓存 JavaScript 前端开发
Vue3——基础内容部分(小满版本)(四)
Vue3——基础内容部分(小满版本)
41 0
|
JavaScript 算法 前端开发
Vue3——基础内容部分(小满版本)(二)
Vue3——基础内容部分(小满版本)
58 0
|
1月前
|
缓存 JavaScript 前端开发
Vue3——Router4教程(小满版本)(二)
Vue3——Router4教程(小满版本)
59 0
|
1月前
|
算法 JavaScript 前端开发
Vue3——Router4教程(小满版本)(一)
Vue3——Router4教程(小满版本)
47 0
|
3月前
|
JavaScript
Vue工具和生态系统:请解释Vue中的mixin是什么?有哪些注意事项?
Vue工具和生态系统:请解释Vue中的mixin是什么?有哪些注意事项?
22 0
|
3月前
|
JavaScript 前端开发 IDE
Vue3【为什么选择Vue框架、Vue简介 、Vue API 风格 、Vue开发前的准备 、Vue项目目录结构 、模板语法、属性绑定 、 】(一)-全面详解(学习总结---从入门到深化)
Vue3【为什么选择Vue框架、Vue简介 、Vue API 风格 、Vue开发前的准备 、Vue项目目录结构 、模板语法、属性绑定 、 】(一)-全面详解(学习总结---从入门到深化)
52 1
|
4月前
【Vue2.0学习】—el与data的两种写法(三十六)
【Vue2.0学习】—el与data的两种写法(三十六)
|
4月前
【Vue2.0】— TodoList案例(十七)
【Vue2.0】— TodoList案例(十七)
|
JavaScript 前端开发
Vue实战笔记(二) 引入Element Plus
Vue实战笔记(二) 引入Element Plus
444 0