Vue3学习手册
myzbx 2025-01-07 14:27 46 浏览
一. Vue3快速上手
1. 认识Vue3
1 了解相关信息
2 性能提升:
3 新增特性
2. 创建vue3项目
1 使用 vue-cli 创建
文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project然后的步骤
2 使用 vite 创建
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev二. Composition API
1. Composition API(常用部分)
文档:
https://composition-api.vuejs.org/zh/api.html
1 setup
2 ref
<template>
  <h2>{{count}}</h2>
  <hr>
  <button @click="update">更新</button>
</template>
<script>
import {
  ref
} from 'vue'
export default {
  /* 在Vue3中依然可以使用data和methods配置, 但建议使用其新语法实现 */
  // data () {
  //   return {
  //     count: 0
  //   }
  // },
  // methods: {
  //   update () {
  //     this.count++
  //   }
  // }
  /* 使用vue3的composition API */
  setup () {
    // 定义响应式数据 ref对象
    const count = ref(1)
    console.log(count)
    // 更新响应式数据的函数
    function update () {
      // alert('update')
      count.value = count.value + 1
    }
    return {
      count,
      update
    }
  }
}
</script>3 reactive
<template>
  <h2>name: {{state.name}}</h2>
  <h2>age: {{state.age}}</h2>
  <h2>wife: {{state.wife}}</h2>
  <hr>
  <button @click="update">更新</button>
</template>
<script>
/* 
reactive: 
    作用: 定义多个数据的响应式
    const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
    响应式转换是“深层的”:会影响对象内部所有嵌套的属性
    内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
*/
import {
  reactive,
} from 'vue'
export default {
  setup () {
    /* 
    定义响应式数据对象
    */
    const state = reactive({
      name: 'tom',
      age: 25,
      wife: {
        name: 'marry',
        age: 22
      },
    })
    console.log(state, state.wife)
    const update = () => {
      state.name += '--'
      state.age += 1
      state.wife.name += '++'
      state.wife.age += 2
    }
    return {
      state,
      update,
    }
  }
}
</script>4 比较Vue2与Vue3的响应式(重要)
vue2的响应式
Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})Vue3的响应式
const proxy = new Proxy(data, {
    // 拦截读取属性值
    get (target, prop) {
        return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
        return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
        return Reflect.deleteProperty(target, prop)
    }
})
proxy.name = 'tom'   <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Proxy 与 Reflect</title>
</head>
<body>
  <script>
    const user = {
      name: "John",
      age: 12
    };
    /* 
    proxyUser是代理对象, user是被代理对象
    后面所有的操作都是通过代理对象来操作被代理对象内部属性
    */
    const proxyUser = new Proxy(user, {
      get(target, prop) {
        console.log('劫持get()', prop)
        return Reflect.get(target, prop)
      },
      set(target, prop, val) {
        console.log('劫持set()', prop, val)
        return Reflect.set(target, prop, val); // (2)
      },
      deleteProperty (target, prop) {
        console.log('劫持delete属性', prop)
        return Reflect.deleteProperty(target, prop)
      }
    });
    // 读取属性值
    console.log(proxyUser===user)
    console.log(proxyUser.name, proxyUser.age)
    // 设置属性值
    proxyUser.name = 'bob'
    proxyUser.age = 13
    console.log(user)
    // 添加属性
    proxyUser.sex = '男'
    console.log(user)
    // 删除属性
    delete proxyUser.sex
    console.log(user)
  </script>
</body>
</html>5 setup细节
<template>
  <h2>App</h2>
  <p>msg: {{msg}}</p>
  <button @click="fn('--')">更新</button>
  <child :msg="msg" msg2="cba" @fn="fn"/>
</template>
<script lang="ts">
import {
  reactive,
  ref,
} from 'vue'
import child from './child.vue'
export default {
  components: {
    child
  },
  setup () {
    const msg = ref('abc')
    function fn (content: string) {
      msg.value += content
    }
    return {
      msg,
      fn
    }
  }
}
</script><template>
  <div>
    <h3>{{n}}</h3>
    <h3>{{m}}</h3>
    <h3>msg: {{msg}}</h3>
    <h3>msg2: {{$attrs.msg2}}</h3>
    <slot name="xxx"></slot>
    <button @click="update">更新</button>
  </div>
</template>
<script lang="ts">
import {
  ref,
  defineComponent
} from 'vue'
export default defineComponent({
  name: 'child',
  props: ['msg'],
  emits: ['fn'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验
  data () {
    console.log('data', this)
    return {
      // n: 1
    }
  },
  beforeCreate () {
    console.log('beforeCreate', this)
  },
  methods: {
    // update () {
    //   this.n++
    //   this.m++
    // }
  },
  // setup (props, context) {
  setup (props, {attrs, emit, slots}) {
    console.log('setup', this)
    console.log(props.msg, attrs.msg2, slots, emit)
    const m = ref(2)
    const n = ref(3)
    function update () {
      // console.log('--', this)
      // this.n += 2 
      // this.m += 2
      m.value += 2
      n.value += 2
      // 分发自定义事件
      emit('fn', '++')
    }
    return {
      m,
      n,
      update,
    }
  },
})
</script>6 reactive与ref-细节
<template>
  <h2>App</h2>
  <p>m1: {{m1}}</p>
  <p>m2: {{m2}}</p>
  <p>m3: {{m3}}</p>
  <button @click="update">更新</button>
</template>
<script lang="ts">
import {
  reactive,
  ref
} from 'vue'
export default {
  setup () {
    const m1 = ref('abc')
    const m2 = reactive({x: 1, y: {z: 'abc'}})
    // 使用ref处理对象  ==> 对象会被自动reactive为proxy对象
    const m3 = ref({a1: 2, a2: {a3: 'abc'}})
    console.log(m1, m2, m3)
    console.log(m3.value.a2) // 也是一个proxy对象
    function update() {
      m1.value += '--'
      m2.x += 1
      m2.y.z += '++'
      m3.value = {a1: 3, a2: {a3: 'abc---'}}
      m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
      console.log(m3.value.a2)
    }
    return {
      m1,
      m2,
      m3,
      update
    }
  }
}
</script>7 计算属性与监视
<template>
  <h2>App</h2>
  fistName: <input v-model="user.firstName"/><br>
  lastName: <input v-model="user.lastName"/><br>
  fullName1: <input v-model="fullName1"/><br>
  fullName2: <input v-model="fullName2"><br>
  fullName3: <input v-model="fullName3"><br>
</template>
<script lang="ts">
/*
计算属性与监视
1. computed函数: 
  与computed配置功能一致
  只有getter
  有getter和setter
2. watch函数
  与watch配置功能一致
  监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
  默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
  通过配置deep为true, 来指定深度监视
3. watchEffect函数
  不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
  默认初始时就会执行第一次, 从而可以收集需要监视的数据
  监视数据发生变化时回调
*/
import {
  reactive,
  ref,
  computed,
  watch,
  watchEffect
} from 'vue'
export default {
  setup () {
    const user = reactive({
      firstName: 'A',
      lastName: 'B'
    })
    // 只有getter的计算属性
    const fullName1 = computed(() => {
      console.log('fullName1')
      return user.firstName + '-' + user.lastName
    })
    // 有getter与setter的计算属性
    const fullName2 = computed({
      get () {
        console.log('fullName2 get')
        return user.firstName + '-' + user.lastName
      },
      set (value: string) {
        console.log('fullName2 set')
        const names = value.split('-')
        user.firstName = names[0]
        user.lastName = names[1]
      }
    })
    const fullName3 = ref('')
    /* 
    watchEffect: 监视所有回调中使用的数据
    */
    /* 
    watchEffect(() => {
      console.log('watchEffect')
      fullName3.value = user.firstName + '-' + user.lastName
    }) 
    */
    /* 
    使用watch的2个特性:
      深度监视
      初始化立即执行
    */
    watch(user, () => {
      fullName3.value = user.firstName + '-' + user.lastName
    }, {
      immediate: true,  // 是否初始化立即执行一次, 默认是false
      deep: true, // 是否是深度监视, 默认是false
    })
    /* 
    watch一个数据
      默认在数据发生改变时执行回调
    */
    watch(fullName3, (value) => {
      console.log('watch')
      const names = value.split('-')
      user.firstName = names[0]
      user.lastName = names[1]
    })
    /* 
    watch多个数据: 
      使用数组来指定
      如果是ref对象, 直接指定
      如果是reactive对象中的属性,  必须通过函数来指定
    */
    watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
      console.log('监视多个数据', values)
    })
    return {
      user,
      fullName1,
      fullName2,
      fullName3
    }
  }
}
</script>8 生命周期
与 2.x 版本生命周期相对应的组合式 API
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
<template>
<div class="about">
  <h2>msg: {{msg}}</h2>
  <hr>
  <button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import {
  ref,
  onMounted,
  onUpdated,
  onUnmounted, 
  onBeforeMount, 
  onBeforeUpdate,
  onBeforeUnmount
} from "vue"
export default {
  beforeCreate () {
    console.log('beforeCreate()')
  },
  created () {
    console.log('created')
  },
  beforeMount () {
    console.log('beforeMount')
  },
  mounted () {
    console.log('mounted')
  },
  beforeUpdate () {
    console.log('beforeUpdate')
  },
  updated () {
    console.log('updated')
  },
  beforeUnmount () {
    console.log('beforeUnmount')
  },
  unmounted () {
     console.log('unmounted')
  },
  setup() {
    const msg = ref('abc')
    const update = () => {
      msg.value += '--'
    }
    onBeforeMount(() => {
      console.log('--onBeforeMount')
    })
    onMounted(() => {
      console.log('--onMounted')
    })
    onBeforeUpdate(() => {
      console.log('--onBeforeUpdate')
    })
    onUpdated(() => {
      console.log('--onUpdated')
    })
    onBeforeUnmount(() => {
      console.log('--onBeforeUnmount')
    })
    onUnmounted(() => {
      console.log('--onUnmounted')
    })
    return {
      msg,
      update
    }
  }
}
</script><template>
  <h2>App</h2>
  <button @click="isShow=!isShow">切换</button>
  <hr>
  <Child v-if="isShow"/>
</template>
<script lang="ts">
import Child from './Child.vue'
export default {
  data () {
    return {
      isShow: true
    }
  },
  components: {
    Child
  }
}
</script>9 自定义hook函数
import { ref, onMounted, onUnmounted } from 'vue'
/* 
收集用户鼠标点击的页面坐标
*/
export default function useMousePosition () {
  // 初始化坐标数据
  const x = ref(-1)
  const y = ref(-1)
  // 用于收集点击事件坐标的函数
  const updatePosition = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  // 挂载后绑定点击监听
  onMounted(() => {
    document.addEventListener('click', updatePosition)
  })
  // 卸载前解绑点击监听
  onUnmounted(() => {
    document.removeEventListener('click', updatePosition)
  })
  return {x, y}
}<template>
<div>
  <h2>x: {{x}}, y: {{y}}</h2>
</div>
</template>
<script>
import {
  ref
} from "vue"
/* 
在组件中引入并使用自定义hook
自定义hook的作用类似于vue2中的mixin技术
自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
*/
import useMousePosition from './hooks/useMousePosition'
export default {
  setup() {
    const {x, y} = useMousePosition()
    return {
      x,
      y,
    }
  }
}
</script>import { ref } from 'vue'
import axios from 'axios'
/* 
使用axios发送异步ajax请求
*/
export default function useUrlLoader<T>(url: string) {
  const result = ref<T | null>(null)
  const loading = ref(true)
  const errorMsg = ref(null)
  axios.get(url)
    .then(response => {
      loading.value = false
      result.value = response.data
    })
    .catch(e => {
      loading.value = false
      errorMsg.value = e.message || '未知错误'
    })
  return {
    loading,
    result,
    errorMsg,
  }
}<template>
<div class="about">
  <h2 v-if="loading">LOADING...</h2>
  <h2 v-else-if="errorMsg">{{errorMsg}}</h2>
  <!-- <ul v-else>
    <li>id: {{result.id}}</li>
    <li>name: {{result.name}}</li>
    <li>distance: {{result.distance}}</li>
  </ul> -->
  <ul v-for="p in result" :key="p.id">
    <li>id: {{p.id}}</li>
    <li>title: {{p.title}}</li>
    <li>price: {{p.price}}</li>
  </ul>
  <!-- <img v-if="result" :src="result[0].url" alt=""> -->
</div>
</template>
<script lang="ts">
import {
  watch
} from "vue"
import useRequest from './hooks/useRequest'
// 地址数据接口
interface AddressResult {
  id: number;
  name: string;
  distance: string;
}
// 产品数据接口
interface ProductResult {
  id: string;
  title: string;
  price: number;
}
export default {
  setup() {
    // const {loading, result, errorMsg} = useRequest<AddressResult>('/data/address.json')
    const {loading, result, errorMsg} = useRequest<ProductResult[]>('/data/products.json')
    watch(result, () => {
      if (result.value) {
        console.log(result.value.length) // 有提示
      }
    })
    return {
      loading,
      result, 
      errorMsg
    }
  }
}
</script>10 toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题: reactive 对象取出的所有属性值都是非响应式的
解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
<template>
  <h2>App</h2>
  <h3>foo: {{foo}}</h3>
  <h3>bar: {{bar}}</h3>
  <h3>foo2: {{foo2}}</h3>
  <h3>bar2: {{bar2}}</h3>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
  将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
  应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
        这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default {
  setup () {
    const state = reactive({
      foo: 'a',
      bar: 'b',
    })
    const stateAsRefs = toRefs(state)
    setTimeout(() => {
      state.foo += '++'
      state.bar += '++'
    }, 2000);
    const {foo2, bar2} = useReatureX()
    return {
      // ...state,
      ...stateAsRefs,
      foo2, 
      bar2
    }
  },
}
function useReatureX() {
  const state = reactive({
    foo2: 'a',
    bar2: 'b',
  })
  setTimeout(() => {
    state.foo2 += '++'
    state.bar2 += '++'
  }, 2000);
  return toRefs(state)
}
</script>11 ref获取元素
利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
<template>
  <h2>App</h2>
  <input type="text">---
  <input type="text" ref="inputRef">
</template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/* 
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
  setup() {
    const inputRef = ref<HTMLElement|null>(null)
    onMounted(() => {
      inputRef.value && inputRef.value.focus()
    })
    return {
      inputRef
    }
  },
}
</script>2. Composition API(其它部分)
1 shallowReactive 与 shallowRef
<template>
  <h2>App</h2>
  <h3>m1: {{m1}}</h3>
  <h3>m2: {{m2}}</h3>
  <h3>m3: {{m3}}</h3>
  <h3>m4: {{m4}}</h3>
  <button @click="update">更新</button>
</template>
<script lang="ts">
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
/* 
shallowReactive与shallowRef
  shallowReactive: 只处理了对象内最外层属性的响应式(也就是浅响应式)
  shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
总结:
  reactive与ref实现的是深度响应式, 而shallowReactive与shallowRef是浅响应式
  什么时候用浅响应式呢?
    一般情况下使用ref和reactive即可,
    如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
*/
export default {
  setup () {
    const m1 = reactive({a: 1, b: {c: 2}})
    const m2 = shallowReactive({a: 1, b: {c: 2}})
    const m3 = ref({a: 1, b: {c: 2}})
    const m4 = shallowRef({a: 1, b: {c: 2}})
    const update = () => {
      // m1.b.c += 1
      // m2.b.c += 1
      // m3.value.a += 1
      m4.value.a += 1
    }
    return {
      m1,
      m2,
      m3,
      m4,
      update,
    }
  }
}
</script>2 readonly 与 shallowReadonly
<template>
  <h2>App</h2>
  <h3>{{state}}</h3>
  <button @click="update">更新</button>
</template>
<script lang="ts">
import { reactive, readonly, shallowReadonly } from 'vue'
/*
readonly: 深度只读数据
  获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
  只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly: 浅只读数据
  创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 
应用场景: 
  在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
*/
export default {
  setup () {
    const state = reactive({
      a: 1,
      b: {
        c: 2
      }
    })
    // const rState1 = readonly(state)
    const rState2 = shallowReadonly(state)
    const update = () => {
      // rState1.a++ // error
      // rState1.b.c++ // error
      // rState2.a++ // error
      rState2.b.c++
    }
    return {
      state,
      update
    }
  }
}
</script>3 toRaw 与 markRaw
<template>
  <h2>{{state}}</h2>
  <button @click="testToRaw">测试toRaw</button>
  <button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
/* 
toRaw: 得到reactive代理对象的目标数据对象
*/
import {
  markRaw,
  reactive, toRaw,
} from 'vue'
export default {
  setup () {
    const state = reactive<any>({
      name: 'tom',
      age: 25,
    })
    const testToRaw = () => {
      const user = toRaw(state)
      user.age++  // 界面不会更新
    }
    const testMarkRaw = () => {
      const likes = ['a', 'b']
      // state.likes = likes
      state.likes = markRaw(likes) // likes数组就不再是响应式的了
      setTimeout(() => {
        state.likes[0] += '--'
      }, 1000)
    }
    return {
      state,
      testToRaw,
      testMarkRaw,
    }
  }
}
</script>4 toRef
<template>
  <h2>App</h2>
  <p>{{state}}</p>
  <p>{{foo}}</p>
  <p>{{foo2}}</p>
  <button @click="update">更新</button>
  <Child :foo="foo"/>
</template>
<script lang="ts">
/*
toRef:
  为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
  区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
  应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
*/
import {
  reactive,
  toRef,
  ref,
} from 'vue'
import Child from './Child.vue'
export default {
  setup () {
    const state = reactive({
      foo: 1,
      bar: 2
    })
    const foo = toRef(state, 'foo')
    const foo2 = ref(state.foo)
    const update = () => {
      state.foo++
      // foo.value++
      // foo2.value++  // foo和state中的数据不会更新
    }
    return {
      state,
      foo,
      foo2,
      update,
    }
  },
  components: {
    Child
  }
}
</script><template>
  <h2>Child</h2>
  <h3>{{foo}}</h3>
  <h3>{{length}}</h3>
</template>
<script lang="ts">
import { computed, defineComponent, Ref, toRef } from 'vue'
const component = defineComponent({
  props: {
    foo: {
      type: Number,
      require: true
    }
  },
  setup (props, context) {
    const length = useFeatureX(toRef(props, 'foo'))
    return {
      length
    }
  }
})
function useFeatureX(foo: Ref) {
  const lenth = computed(() => foo.value.length)
  return lenth
}
export default component
</script>5 customRef
<template>
  <h2>App</h2>
  <input v-model="keyword" placeholder="搜索关键字"/>
  <p>{{keyword}}</p>
</template>
<script lang="ts">
/*
customRef:
  创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
需求: 
  使用 customRef 实现 debounce 的示例
*/
import {
  ref,
  customRef
} from 'vue'
export default {
  setup () {
    const keyword = useDebouncedRef('', 500)
    console.log(keyword)
    return {
      keyword
    }
  },
}
/* 
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: number
  return customRef((track, trigger) => {
    return {
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      set(newValue: T) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 告诉Vue去触发界面更新
          trigger()
        }, delay)
      }
    }
  })
}
</script>6 provide 与 inject
<template>
  <h1>父组件</h1>
  <p>当前颜色: {{color}}</p>
  <button @click="color='red'">红</button>
  <button @click="color='yellow'">黄</button>
  <button @click="color='blue'">蓝</button>
  <hr>
  <Son />
</template>
<script lang="ts">
import { provide, ref } from 'vue'
/* 
- provide` 和 `inject` 提供依赖注入,功能类似 2.x 的 `provide/inject
- 实现跨层级组件(祖孙)间通信
*/
import Son from './Son.vue'
export default {
  name: 'ProvideInject',
  components: {
    Son
  },
  setup() {
    const color = ref('red')
    provide('color', color)
    return {
      color
    }
  }
}
</script><template>
  <div>
    <h2>子组件</h2>
    <hr>
    <GrandSon />
  </div>
</template>
<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
  components: {
    GrandSon
  },
}
</script><template>
  <h3 :style="{color}">孙子组件: {{color}}</h3>
</template>
<script lang="ts">
import { inject } from 'vue'
export default {
  setup() {
    const color = inject('color')
    return {
      color
    }
  }
}
</script>7 响应式数据的判断
3. 手写组合API
1 shallowReactive 与 reactive
const reactiveHandler = {
  get (target, key) {
    if (key==='_is_reactive') return true
    return Reflect.get(target, key)
  },
  set (target, key, value) {
    const result = Reflect.set(target, key, value)
    console.log('数据已更新, 去更新界面')
    return result
  },
  deleteProperty (target, key) {
    const result = Reflect.deleteProperty(target, key)
    console.log('数据已删除, 去更新界面')
    return result
  },
}
/* 
自定义shallowReactive
*/
function shallowReactive(obj) {
  return new Proxy(obj, reactiveHandler)
}
/* 
自定义reactive
*/
function reactive (target) {
  if (target && typeof target==='object') {
    Object.entries(target).forEach(([key, value]) => {
      target[key] = reactive(value)
    })
    return new Proxy(target, reactiveHandler)
  }
  return target
}
/* 测试自定义shallowReactive */
const proxy = shallowReactive({
  a: {
    b: 3
  }
})
proxy.a = {b: 4} // 劫持到了
proxy.a.b = 5 // 没有劫持到
/* 测试自定义reactive */
const obj = {
  a: 'abc',
  b: [{x: 1}],
  c: {x: [11]},
}
const proxy = reactive(obj)
console.log(proxy)
proxy.b[0].x += 1
proxy.c.x[0] += 12 shallowRef 与 ref
/*
自定义shallowRef
*/
function shallowRef(target) {
  const result = {
    _value: target, // 用来保存数据的内部属性
    _is_ref: true, // 用来标识是ref对象
    get value () {
      return this._value
    },
    set value (val) {
      this._value = val
      console.log('set value 数据已更新, 去更新界面')
    }
  }
  return result
}
/* 
自定义ref
*/
function ref(target) {
  if (target && typeof target==='object') {
    target = reactive(target)
  }
  const result = {
    _value: target, // 用来保存数据的内部属性
    _is_ref: true, // 用来标识是ref对象
    get value () {
      return this._value
    },
    set value (val) {
      this._value = val
      console.log('set value 数据已更新, 去更新界面')
    }
  }
  return result
}
/* 测试自定义shallowRef */
const ref3 = shallowRef({
  a: 'abc',
})
ref3.value = 'xxx'
ref3.value.a = 'yyy'
/* 测试自定义ref */
const ref1 = ref(0)
const ref2 = ref({
  a: 'abc',
  b: [{x: 1}],
  c: {x: [11]},
})
ref1.value++
ref2.value.b[0].x++
console.log(ref1, ref2)3 shallowReadonly 与 readonly
const readonlyHandler = {
  get (target, key) {
    if (key==='_is_readonly') return true
    return Reflect.get(target, key)
  },
  set () {
    console.warn('只读的, 不能修改')
    return true
  },
  deleteProperty () {
    console.warn('只读的, 不能删除')
    return true
  },
}
/* 
自定义shallowReadonly
*/
function shallowReadonly(obj) {
  return new Proxy(obj, readonlyHandler)
}
/* 
自定义readonly
*/
function readonly(target) {
  if (target && typeof target==='object') {
    if (target instanceof Array) { // 数组
      target.forEach((item, index) => {
        target[index] = readonly(item)
      })
    } else { // 对象
      Object.keys(target).forEach(key => {
        target[key] = readonly(target[key])
      })
    }
    const proxy = new Proxy(target, readonlyHandler)
    return proxy 
  }
  return target
}
/* 测试自定义readonly */
/* 测试自定义shallowReadonly */
const objReadOnly = readonly({
  a: {
    b: 1
  }
})
const objReadOnly2 = shallowReadonly({
  a: {
    b: 1
  }
})
objReadOnly.a = 1
objReadOnly.a.b = 2
objReadOnly2.a = 1
objReadOnly2.a.b = 24 isRef, isReactive 与 isReadonly
/* 
判断是否是ref对象
*/
function isRef(obj) {
  return obj && obj._is_ref
}
/* 
判断是否是reactive对象
*/
function isReactive(obj) {
  return obj && obj._is_reactive
}
/* 
判断是否是readonly对象
*/
function isReadonly(obj) {
  return obj && obj._is_readonly
}
/* 
是否是reactive或readonly产生的代理对象
*/
function isProxy (obj) {
  return isReactive(obj) || isReadonly(obj)
}
/* 测试判断函数 */
console.log(isReactive(reactive({})))
console.log(isRef(ref({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))三. 其它新组件和API
1. 新组件
1 Fragment(片断)
<template>
    <h2>aaaa</h2>
    <h2>aaaa</h2>
</template>2 Teleport(瞬移)
<template>
  <button @click="modalOpen = true">
      Open full screen modal! (With teleport!)
  </button>
  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        I'm a teleported modal! 
        (My parent is "body")
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </div>
  </teleport>
</template>
<script>
import { ref } from 'vue'
export default {
  name: 'modal-button',
  setup () {
    const modalOpen = ref(false)
    return {
      modalOpen
    }
  }
}
</script>
<style>
.modal {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background-color: rgba(0,0,0,.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: white;
  width: 300px;
  height: 300px;
  padding: 5px;
}
</style><template>
  <h2>App</h2>
  <modal-button></modal-button>
</template>
<script lang="ts">
import ModalButton from './ModalButton.vue'
export default {
  setup() {
    return {
    }
  },
  components: {
    ModalButton
  }
}
</script>3 Suspense(不确定的)
<template>
  <Suspense>
    <template v-slot:default>
      <AsyncComp/>
      <!-- <AsyncAddress/> -->
    </template>
    <template v-slot:fallback>
      <h1>LOADING...</h1>
    </template>
  </Suspense>
</template>
<script lang="ts">
/* 
异步组件 + Suspense组件
*/
// import AsyncComp from './AsyncComp.vue'
import AsyncAddress from './AsyncAddress.vue'
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
  setup() {
    return {
    }
  },
  components: {
    AsyncComp,
    AsyncAddress
  }
}
</script><template>
  <h2>AsyncComp22</h2>
  <p>{{msg}}</p>
</template>
<script lang="ts">
export default {
  name: 'AsyncComp',
  setup () {
    // return new Promise((resolve, reject) => {
    //   setTimeout(() => {
    //     resolve({
    //       msg: 'abc'
    //     })
    //   }, 2000)
    // })
    return {
      msg: 'abc'
    }
  }
}
</script><template>
<h2>{{data}}</h2>
</template>
<script lang="ts">
import axios from 'axios'
export default {
  async setup() {
    const result = await axios.get('/data/address.json')
    return {
      data: result.data
    }
  }
}
</script>2. 其他新的API
1 全新的全局API
2 将原来的全局API转移到应用对象
3 模板语法变化
相关推荐
- 如何设计一个优秀的电子商务产品详情页
- 
        加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面... 
- 怎么在JS中使用Ajax进行异步请求?
- 
        大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加... 
- 中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革
- 
        前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很... 
- 前端监控 SDK 开发分享_前端监控系统 开源
- 
        一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的... 
- Ajax 会被 fetch 取代吗?Axios 怎么办?
- 
        大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch... 
- 前端面试题《AJAX》_前端面试ajax考点汇总
- 
        1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实... 
- Ajax 详细介绍_ajax
- 
        1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的... 
- 6款可替代dreamweaver的工具_替代powerdesigner的工具
- 
        dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver... 
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
- 
        接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝... 
- 福斯《死侍》发布新剧照 "小贱贱"韦德被改造前造型曝光
- 
        时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预... 
- 2021年超详细的java学习路线总结—纯干货分享
- 
        本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象... 
- 不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!
- 
        Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人... 
- 2025 年 Python 爬虫四大前沿技术:从异步到 AI
- 
        作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易... 
- 最贱超级英雄《死侍》来了!_死侍超燃
- 
        死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/... 
- 停止javascript的ajax请求,取消axios请求,取消reactfetch请求
- 
        一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求... 
- 一周热门
- 最近发表
- 标签列表
- 
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)
 
