vue3 知识点 和 对比 部分总结, 更新中。。。。。

1. node 环境 > 16

2. 给 VS code 安装 Volar 扩展插件

3. 整个应用程序的app暴露出一个全局配置选项

const app = createApp(App)

// 应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项
app.config.errorHandler = (err)=> {
    console.log("程序的错误信息。。。")
    console.log(err)
}

app.mount("#app")

4. 整个应用程序的app暴露出一个全局组件注册方法

const app = createApp(App)

// 应用实例还提供了一些方法来注册应用范围内可用的资源,例如注册一个组件:
// app.component('全局组件名称', 一个全局组件)

app.mount("#app")

5. 给整个应用程序添加全局数据

// 添加全局数据
app.config.globalProperties = {
    sx: 777
}

// 接下来在组件的模板中可直接获取使用 
<div>
        {{sx}}
</div>

// 在组件脚本中, 需要先获取全局实例对象
<script setup lang="ts">
    import { getCurrentInstance } from 'vue'
    console.log(getCurrentInstance())
</script>
20220928095455148.png

6. 创建一个响应式的 对象 或 数组

// 注意: reactive() 返回的是一个原始对象的 Proxy
const raw = {}
const proxy = reactive(raw)
只有代理对象是响应式的,更改原始对象不会触发更新。

为保证访问代理的一致性,
对同一个原始对象调用 reactive() 会总是返回同样的代理对象,
而对一个已存在的代理对象调用 reactive() 会返回其本身:

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:
const proxy = reactive({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false

因为 Vue 的响应式系统是通过属性访问进行追踪的,
因此我们必须始终保持对该响应式对象的相同引用。
这意味着我们不可以随意地“替换”一个响应式对象,
因为这将导致对初始引用的响应性连接丢失:
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++

// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)

// 组合式 api
<script setup lang="ts">

    // 创建一个响应式的 对象 或 数组
    import { reactive } from 'vue'
    const state = reactive({ name: 0 })

    function zengjia(cc: any) {
        state.name = cc
    }
</script>

// 选项式 api
<script>
import { reactive } from '_vue@3.2.39@vue'

    export default {
        setup() {
            const state = reactive({name: "张三"})
            function zengjia(cc) {
                state.name = cc
            }

            return {
                state,
                zengjia
            }
        }
    }

</script>

7. nextTick() 等 编程了 全局api

import { nextTick } from 'vue'

8. 深层响应式对象 & 浅层响应式对象

9. 响应式变量

Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:
import { ref } from 'vue'

const count = ref(0)

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象
当值为对象类型时,会用 reactive() 自动转换它的 .value。
const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }


ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性
const obj = {
  foo: ref(1),
  bar: ref(2)
}

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)

// 仍然是响应式的
const { foo, bar } = obj

简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。


ref的解包
ref在模板中解包
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value
<button @click="increment">
    {{ count }} <!-- 无需 .value -->
  </button>
  
function increment() {
  count.value++ // 需要.value
}

ref 在响应式对象中的解包
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样

数组和集合类型的 ref 解包
跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

10. 计算属性

<script setup>
import { reactive, computed } from 'vue'

const xxx = computed(() => {
  return 数据 ? '111' : '222'
})
</script>

<template>
  <span>{{ xxx }}</span>
</template>

computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 xxx.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value

11. 计算属性 & 方法

计算属性值会基于其响应式依赖被缓存

一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 依赖的响应式数据 不改变,无论多少次访问 计算属性 都会立即返回先前的计算结果,而不用重复执行 getter 函数。

方法调用总是会在重渲染发生时再次执行函数。

像下面的计算属性永远不会更新, 因为 Date.now() 并不是一个响应式依赖:

const now = computed(() => Date.now())

12. 计算属性为什么要缓存

想象一下我们有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list。没有缓存的话,我们会重复执行非常多次 list 的计算函数,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。

13. 可写计算属性

只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:

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

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

现在当你再运行 fullName.value = 'John Doe' 时,setter 会被调用而 firstNamelastName 会随之更新。

另注意:

在计算属性中使用 reverse()sort() 的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:

- return 数组.reverse()
+ return [...数组].reverse()

14. 数组变化侦测

变更方法

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。

变更: 就是改变数组内的元素, 但不会生成新的数组

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替换一个数组

例如 filter()concat()slice()等,这些方法都不会更改原数组,而总是返回一个新数组

在这种情况下, 我们需要用新生成的数组 来 替换 老的数组

比如 :item 是一个数组的 ref
那么: 可以这样来操作

item.value = item.value.concat(...)

其本质就是 , 保证 ref 的引用不变, 还是响应式的

你可能认为这将导致 Vue 丢弃现有的 DOM 并重新渲染整个列表——幸运的是,情况并非如此。Vue 实现了一些巧妙的方法来最大化对 DOM 元素的重用,因此用另一个包含部分重叠对象的数组来做替换,仍会是一种非常高效的操作。

15. 在点击事件中 访问事件对象 event

<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>

function warn(message, event) {
  // 这里可以访问原生事件
  if (event) {
    event.preventDefault()
  }
  alert(message)
}

16. 生命周期

lifecycle.16e4c08e.png

17. 监听器 watch() 和 watchEffect()

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。
默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。

<script setup>
import { ref, watch } from 'vue'
  
  // 响应式
  // 响应式对象
  // 响应式变量

const aaa = ref(url)
const bbb = ref(...)

// 可以直接侦听一个 ref
watch(aaa, async (新值, 老值) => {
  // 做一些处理
    // 也可以在次进行其他ref的修改
  // 也可以执行异步函数
})
</script>

你不能直接侦听响应式对象的属性值

const obj = reactive({ count: 0 })

// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

这里需要用一个返回该属性的 getter 函数


// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

简单来说,
侦听reactive数据

const state = reactive({ nickname: "xiaofan", age: 20 });
// 修改age值时会触发 watch的回调
watch(
  () => state.age,
  (curAge, preAge) => {
    console.log("新值:", curAge, "老值:", preAge);
  }
);

侦听ref数据

const year = ref(0);
watch(year, (newVal, oldVal) => {
  console.log("新值:", newVal, "老值:", oldVal);
});

同时侦听多个数据

// 当我们需要侦听多个数据源时, 可以进行合并, 同时侦听多个数据:
watch([() => state.age, year], ([curAge, newVal], [preAge, oldVal]) => {
console.log("新值:", curAge, "老值:", preAge); console.log("新值:", newVal,
"老值:", oldVal); });

侦听复杂的嵌套对象

const state = reactive({
  room: {
    id: 100,
    attrs: {
      size: "140平方米",
      type: "三室两厅",
    },
  },
});
// 注意在此使用的 watch() 方法中的第三个参数: deep:true
watch(
  () => state.room,
  (newType, oldType) => {
    console.log("新值:", newType, "老值:", oldType);
  },
  { deep: true }
);

//  补充: 默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?
// 其实使用也很简单, 给第三个参数中设置immediate: true 即可。

stop 停止监听
组件中创建的watch监听,会在组件被销毁时自动停止
如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值

const xxx = watch(.....);


setTimeout(()=>{
    // 停止监听
    xxx()
}, 3000)

和 watchEffect() 对比

// watch已经能满足监听的需求,为什么还要有watchEffect呢
// 还是观察对比, 找区别吧

watchEffect(
     () => {
        console.log(数据1);
        console.log(数据2);
      }
);

上方代码执行结果如下:
执行结果首先打印一次数据1数据2的值;
然后每隔一秒,再打印数据1数据2的值
从上面的代码可以看出, watchEffect 并没有像 watch 一样需要先传入依赖,
watchEffect 会自动收集依赖, 只要指定一个回调函数即可。
在组件初始化时, 会先执行一次来收集依赖,
然后当收集到的依赖中数据发生变化时, 就会再次执行回调函数。
所以总结对比如下:

  • watchEffect 不需要手动传入依赖
  • watchEffect 会先执行一次用来自动收集依赖
  • watchEffect 无法获取到变化前的值, 只能获取变化后的值

18. 事件

在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件

<button @click="$emit('someEvent')">click me</button>

可以给事件带参数

<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>

$emit 方法不能在组件的 <script setup> 部分中
可以显式地通过 defineEmits() 宏来声明

<script setup>
  // defineEmits() 会返回一个相同作用的函数供我们使用
  const emit = defineEmits(['inFocus', 'submit'])

  function buttonClick() {
    emit('submit')
  }
</script>

defiineEmits() 宏函数使用的注意事项

  • defineEmits()不能在子函数中使用, 它必须直接放置在 <script setup> 的顶级作用域下

  • 如果你在选项式api组件中,显式地使用了 setup 函数, 而不是 <script setup>,则事件需要通过 emits 选项来定义,emit 函数也被暴露在 setup() 的上下文对象上

    export default {
      emits: ['inFocus', 'submit'],
      setup(props, ctx) {
        ctx.emit('submit')
      }
    }
    
  • 这个 emits 选项还支持对象语法,它允许我们对触发事件的参数进行验证:

    <script setup>
    const emit = defineEmits({
      submit: function ({ email, password }) {
        // 该函数也称为 事件校验 函数
        // 比如该 提交校验 函数
        if (email && password) {
          return true
        } else {
          console.warn('Invalid submit event payload!')
          return false
        }
        // 通过返回值为 `true` 还是为 `false` 来判断
        // 验证是否通过
      }
    })
    </script>
    
  • 如果你正在搭配 TypeScript 使用 <script setup>,也可以使用纯类型标注来声明触发的事件:

    <script setup lang="ts">
    const emit = defineEmits<{
        (e: 'change', id: number): void
        (e: 'update', value: string): void
      }>()
    </script>
    

19. 事件 配合 v-model 来完成 组件内的 input 输入框数据绑定

事件 配合 v-model 来完成 组件内的 input 输入框数据绑定

<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>


<!-- 使用改 组件 -->
<CustomInput v-model="searchText" />


<!-- 另: 注意一下 -->
<!-- 普通标签上 v-model 的拆分 -->
<input v-model="searchText" />

<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

<!-- 组件上 v-model 的拆分 -->
<组件
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>


20. getter setter 配合 v-model 来完成 组件内的 input 输入框数据绑定

<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>
<MyComponent v-model:title="bookTitle" />

21. 依赖注入(vue3, 所以只能出现在 setup 中)

provide-inject.3e0505e4.png
  • 数据提供者
// 在组件内使用
import { provide } from 'vue'
provide(名称 , 值)

// 给整个app使用
app.provide(名称 , 值)

// 值 也可以是 响应式的值, 
// 使后代组件可以由此和提供者建立响应式的联系
// 一个组件可以多次调用 provide(),
// 使用不同的注入名,注入不同的依赖值。

// 给值套上 readonly, 来不让注入方修改值
app.provide(名称 , readonly(值))

  • 数组注入
// 使用 inject() 函数
const aaa = inject('名称')

// 没有提供者的时候, 可以使用默认值
// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('名称', '这是默认值')

// 默认值 也可以是一个工厂函数
const value = inject('名称', ()=>{。。。。})


22. 异步组件加载


// 异步组件 的 局部注册
import { defineAsyncComponent } from 'vue'

const 异步组件 = defineAsyncComponent(() => {
  
  // 情况1:
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
  })
  
  // 情况2: 
  return import('./组件.vue')
  
  
})

// 异步组件 的 全局注册
app.component('异步组件', defineAsyncComponent(() =>
  import('./组件.vue')
))

// 附加属性或功能
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),

  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

// ***也可以搭配 <Suspense> 组件


23. 内部组件 --- <Suspense>

<Suspense>让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,
并可以在等待时渲染一个加载状态。

<Suspense> 可以等待的异步依赖有两种

  1. 带有异步 setup() 钩子的组件。这也包含了使用 <script setup> 时有顶层 await 表达式的组件。

    export default {
      async setup() {
        const res = await fetch(...)
        const posts = await res.json()
        return {
          posts
        }
      }
    }
    
  2. 异步组件

    /*
    异步组件默认就是“suspensible”的。
    这意味着如果组件关系链上有一个 <Suspense 菊花>,
    那么这个异步组件就会被当作这个 <Suspense> 的一个异步依赖。
    
    在这种情况下,加载状态是由 <Suspense> 控制,
    而该组件自己的加载、报错、延时和超时等选项都将被忽略。
    
    异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制,
    并让组件始终自己控制其加载状态。
    */
    
    // 在 <script setup>中,顶层 await 表达式会自动让该组件成为一个异步依赖:
    <script setup>
    const res = await fetch(...)
    const posts = await res.json()
    </script>
    
    <template>
      {{ posts }}
    </template>
    

<Suspense> 组将用法:

<Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <Dashboard />

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

<Suspense> 组件有两个插槽:#default#fallback

#default#fallback 中都只能接受一个根元素

在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。

<Suspense> 组件会触发三个事件:pendingresolvefallback

我们常常会将 <Suspense><Transition><Keeplive><RouterView> 等组件结合。
要保证这些组件都能正常工作,嵌套的顺序非常重要。

<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- 主要内容 -->
          <component :is="Component"></component>

          <!-- 加载中状态 -->
          <template #fallback>
            正在加载...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

24. ctx 属性

vue3.x 开发环境之下, 可以看到$router$store、声明的变量和方法等
但 在 vue3.x 生产换件之下, 生产环境的ctx$router$store没有了,其他属性也都没有了,不能通过ctx.$routerctx.$store访问router和store,因此ctx可以说对我们没有用,应该避免在代码中使用ctx

所以, : 对于网上一些其他文档使用ctx.$routerctx.$store访问router和store的应该小心避坑,注意开发环境和生产环境的差别

25. 关于生命周期中的一些区别:

执行顺序方面:

3中的setup相当于2中的beforeCreate和created的合并。
vue3.x中会先执行`setup`方法,再执行兼容2.x的其他方法,比如`data`、`computed`、`watch`等,
并且在`setup`执行过程中,无法访问`data`中定义的属性,因为此时还未执行到`data`方法
又并且,setup 函数只走一遍

mount挂载的区别:

  • 2.x会使用挂载元素的outerHTML作为template,并替换挂载元素
  • 3.x会使用挂载元素的innerHTML作为template,并且只替换挂载元素的子元素

26. 关于元素或组件引用的问题

2.x可以在组件挂载之后通过this.$el访问组件根元素
3.x中没有this了, 一切都发生变化了:

3.x去掉了this,但支持了Fragment,所以this.$el没有存在的意义,建议通过refs访问DOM
在组合式 api 中, reactive refstemplate refs 的概念已经是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup() 中声明一个 ref 并返回它

<template>
  <div ref="root"></div>
</template>

<script>
  import { ref, onMounted, getCurrentInstance } from 'vue'

  export default {
    setup() {
      const vm = getCurrentInstance()
      const root = ref(null)

      onMounted(() => {
        // 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
        console.log(root.value) // <div/>
        console.log(vm.refs.root) // <div/>
        console.log(root.value === vm.refs.root) // true
      })

      return {
        root
      }
    }
  }
</script>

在 v-for 中 使用 refs

<template>
  <div v-for="(item, i) in list" :key="i" :ref="el => { divs[i] = el }">
    {{ item }}
  </div>
</template>
<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'
  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])

      // 确保在每次变更之前重置引用
      onBeforeUpdate(() => {
        divs.value = []
      })

      return {
        list,
        divs
      }
    }
  }
</script>

27. 自定义指令 directive 的改动

// vue2.x
export default {
  name: 'YourDirectiveName',
  bind(el, binding, vnode, oldVnode) {},
  inserted(...) {},
  update(...) {},
  componentUpdated(...) {},
  unbind(...) {}
}

// vue3.x
export default {
  beforeMount(el, binding, vnode, oldVnode) {},
  mounted(...) {},
  beforeUpdate(...) {},
  updated(...) {},
  beforeUnmount(...) {},
  unmounted() {...}
}

28. 对 render 方法的 改动

vue、react都提供了render方法渲染html模板,直接使用render方法的还是比较少,毕竟有templateJSX,对于确实需要自定义render方法渲染模板内容的,具体变动如下:

// vue2.x
export default {
  render(h) {
    return h('div')
  }
}

// vue3.x
import { h } from 'vue'
export default {
  render() {
    return h('div')
  }
}

29. 对事件总线 EventBus 的改动

在Vue2.x中可以通过EventBus的方法来实现组件通信

// 声明实例
var EventBus = new Vue()
Vue.prototype.$globalBus = EventBus
// 组件内调用
this.$globalBus.$on('my-event-name', callback)
this.$globalBus.$emit('my-event-name', data)

在vue3.x中移除了 $on$off等方法,而是推荐使用mitt方案来代替:

// 声明实例
import mitt from 'mitt'
const emitter = mitt()


// 组件内调用
// 监听所有事件
emitter.on('*', (type, e) => console.log(type, e))

// 监听个别事件
emitter.on('my-event-name', callback)
emitter.emit('my-event-name', data)


// 清除所有事件
emitter.all.clear()

30. 状态管理---vuex的区别

创建

// Vue2
export default new Vuex.Store({
  state: {
    count:1
  },
  mutations: {
    inc(state){
      state.count ++ 
    }
  },
  actions: {
  },
  modules: {
  }
})

// Vue3
export default Vuex.createStore({
  state: {
    count:1
  },
  mutations: {
    add(state){
      state.count ++ 
    }
  },
  actions: {
  },
  modules: {
  }
});

使用

// Vue2
export default {
  name: "Home",
  data() {
    return {
        state: this.$store.state
    };
  },
  computed: {
    double() {
      return this.$store.state.count * 2;
    },
  },
  methods: {
    add() {
      this.$store.commit("add");
    }
  }
};
// Vue3
import { computed,  reactive } from "vue";
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore()
    const state = store.state
    const double = computed(() => store.state.count * 2)

    const add = () => {
      store.commit("add");
    };
    return { state, add ,double};
  }
};

31. 在vue3中自定义 hook 函数

类似的, 钩子函数以 use 开头
创建 usePerosn.js 文件

import { computed, reactive } from "vue"

export default function (per = {name: "zhangsan", age:12}) {
    
    const ren = reactive(per)
    const changeName = (name) => {
        ren.name = name
    }
    const zengjiaAge = (num) => {
        ren.age = ren.age + num
    }
    const getBirth = computed(()=> {
        return 2022 - ren.age
    })
    return {
        ren , 
        changeName,
        zengjiaAge,
        getBirth
    }
}

在组件中 使用 该 person 钩子

<template>
    <div>
        ren的信息{{JSON.stringify(ren)}}
        
        <button @click="changeName('wangwu')">修改名称</button>
        <button @click="zengjiaAge(2)">增加年龄</button>
        <hr>
        {{getBirth}}年出生的
    </div>
</template>

<script setup>
    import usePerson from "./usePerson"

    const {ren , changeName , zengjiaAge , getBirth} = usePerson();

</script>

32. teleport , 将其所包含的组件的层级结构显示到特定的 地方

// 比如说组件A这样定义
<template>
  <teleport to="#任何一个div的id>
    <div class="dialog">
      比如说弹框组件
    </div>
  </teleport>
</template>

// 加入home界面要使用组件A这个弹框
// home界面
<A v-if="is_show" />

// 这样就是: 虽然home界面通过数据控制了弹框组件A,
// 但其实该弹框组件A是展示在 其他DIV中的, 而不是 home 界面里面
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容