【送书】前端系列14集-Vue3-setup

news2024/11/24 19:30:14

送书活动:挑选1名粉丝同学哦 免费包邮送。
截止时间:2023/5/18号  19 : 00
参与底部评论区说
说:请在评论中分享你的阅读收获。

前端工程化:基于Vue.js 3.0的设计与实践实战

f55af1f836ff577901e4c36fe12ba380.jpeg

页面浏览量(Page View,PV)和访客数(Unique Visitors,UV)

// index.vue
<template>
  <a-button @click="onClick">查询</a-button>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

@Component({})
export default class Page extends Vue {
  // PV 埋点
  mounted() {
    window.DATracker.track('page_id', {})
  }

  // 交互埋点
  onClick() {
    window.DATracker.track('event_id', {})
  }
}
</script>

针对页面交互埋点,可以统一封装一个 trackEvent 方法:

// utils/track.ts

/**
 * 日志上报
 * @param id 事件id
 * @param data 上报数据
 */
export const trackEvent = (id, data = {}) => {
  window.DATracker.track(id, data)
}

点击事件的处理相对比较简单,每次点击触发数据上报即可:

// src/directives/track/click.js
import { sendUBT } from "../../utils"

export default class Click {
  add(entry) {
    // console.log("entry", entry);
    const traceVal = entry.el.attributes["track-params"].value
    const traceKey = entry.el.attributes["trace-key"].value
    const { clickAction, detail } = JSON.parse(traceVal)
    const data = {
      action: clickAction,
      detail,
    }
    entry.el.addEventListener("click", function() {
      console.log("上报点击埋点", JSON.parse(traceVal))
      console.log("埋点key", traceKey)
      sendUBT(traceKey, data)
    })
  }
}
  • v-track:click|exposure

  • v-track:exposure

自动化的形式,去添加这些埋点呢?

8785d5cf8a7eedc95fe7aafeb6d2ff86.png
image.png
SysLoginController
SysLoginService
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author
 *
 * 跨域访问配置
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }

}
server: {
        // host: '0.0.0.0',
        // 反向代理解决跨域
        proxy: {
            [env.VITE_APP_PORT]: {
                target: 'htts://',
                changeOrigin: true,
                rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_PORT), '')
            }
        }
        // open: true, // 运行是否自动打开浏览器
        // port: Number(env.VITE_APP_PORT),
    },
// vue.config.js

const env = process.env;

module.exports = {
  devServer: {
    proxy: {
      [env.VITE_APP_PORT]: {
        target: 'htts://example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_PORT), '')
      }
    },
    open: true, // 运行是否自动打开浏览器
    port: Number(env.VITE_APP_PORT),
  },
};
89f16d3062a4262176013d3f453ace0c.png
image.png
37c273ccc6bb0a78badddc4be5cf59f8.png
image.png
server: {
        // host: '0.0.0.0',
        // 反向代理解决跨域
        proxy: {
            '/api': {
                target: 'http://xxx:1606',
                changeOrigin: true,
                secure: false,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
        // open: true, // 运行是否自动打开浏览器
        // port: Number(env.VITE_APP_PORT),
    },
css: {
        // CSS 预处理器
        preprocessorOptions: {
            //define global scss variable
            scss: {
                javascriptEnabled: true,
                additionalData: `@use "@/styles/variables.scss" as *;`
            }
        }
    },

这是一个配置项,用于预处理 CSS 代码。在这个配置中,我们可以指定使用的预处理器以及其配置选项。其中,scss 是一种流行的 CSS 预处理器。javascriptEnabled: true 表示在 SCSS 中可以使用 JavaScript 语法。additionalData 属性允许我们定义全局的 SCSS 变量,该变量将被引入到所有的 SCSS 文件中。在这个示例中,使用 @use 指令将 variables.scss 文件引入,并将其中的所有内容作为全局变量使用。

Vue.js 的 toRaw 方法是一个用于深度遍历响应式对象并返回其非响应式原始值的方法。它可用于访问由 Vue 3 中的 reactiverefcomputed 创建的响应式对象中的原始数据。

在 Vue.js 中,对一个响应式对象进行操作时,Vue.js 会将其包装在一个代理对象内部,以便追踪该对象上属性的变化,并在需要时更新视图。但是有些情况下,我们需要直接访问对象的原始值而不是其代理对象。这时就可以使用 toRaw 方法来获取原始值。

例如:

import { reactive, toRaw } from 'vue'

const state = reactive({ count: 0 })

console.log(state.count) // 0
console.log(toRaw(state).count) // 0

state.count++

console.log(state.count) // 1
console.log(toRaw(state).count) // 0

在上述示例中,我们创建了一个包含计数器的响应式对象 state。然后,我们分别使用 state.counttoRaw(state).count 访问计数器的值。当我们增加计数器的值时,state.count 的值被更新为 1,但 toRaw(state).count 仍然为初始值 0,因为它没有受到反应性系统的管理。

在这段代码中,变量 params 被使用 toRaw 函数复制,然后使用 Object.assign 分配给一个新对象。结果对象 paramsInitparams 的浅拷贝。

使用 toRaw 的目的是创建一个没有响应属性的对象副本。换句话说,如果 params 是一个可响应的对象(例如使用 Vue.js 的 reactive 函数创建的对象),则生成的 paramsInit 对象将不具有响应性。

在 JavaScript 中,使用 Object.assign({}, obj) 是一种常见的创建对象浅拷贝的方法。它创建一个新的空对象,并将源对象 (obj) 的所有可枚举自有属性复制到目标对象(即空对象)中。Object.assign 中第一个参数的 {} 表示我们正在创建一个新的空对象,该对象将用作目标对象。

总体而言,这段代码创建了 params 的非响应式副本,并将其分配给一个新变量 paramsInit

events
size-change page-size 改变时触发
current-change current-page 改变时触发
prev-click 用户点击上一页按钮改变当前页时触发
next-click 用户点击下一页按钮改变当前页时触发
background 是否为分页按钮添加背景色 
page-size / v-model:page-size 每页显示条目个数
default-page-size 每页显示条目数的初始值
total 总条目数
page-count 总页数, total 和 page-count 设置任意一个就可以达到显示页码的功能;如果要支持 page-sizes 的更改,则需要使用 total 属性
pager-count 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
current-page / v-model:current-page 当前页数
default-current-page 当前页数的初始值
layout 组件布局,子组件名用逗号分隔
page-sizes 每页显示个数选择器的选项设置
popper-class 每页显示个数选择器的下拉框类名
prev-text 替代图标显示的上一页文字
prev-icon 上一页的图标, 比 prev-text 优先级更高
next-text 替代图标显示的下一页文字
next-icon 下一页的图标, 比 next-text 优先级更高
disabled 是否禁用分页
hide-on-single-page 只有一页时是否隐藏
warning
total 和 page-count 必须传一个,不然组件无法判断总页数;优先使用 page-count;
如果传入了 current-page,必须监听 current-page 变更的事件(@update:current-page),否则分页切换不起作用;
如果传入了 page-size,且布局包含 page-size 选择器(即 layout 包含 sizes),必须监听 page-size 变更的事件(@update:page-size),否则分页大小的变化将不起作用。

如果要监听 current-page 和 page-size 的改变,使用 v-model 双向绑定是个更好的选择。

定义了一个名为 pager 的常量。

computed() 函数用于创建计算属性。在此例中,通过传入一个对象给 computed() 函数来创建一个计算属性。该对象有两个方法:getset

get() 方法返回 props.modelValue,即组件的 modelValue 属性,作为计算属性的值。

set() 方法接收一个参数 value,并在调用时使用 emit() 方法触发一个事件,以更新 modelValue 属性。该事件名称是 'update:modelValue',它由 defineEmits() 函数定义。

当计算属性的值被改变时,set() 方法将被调用,并触发 'update:modelValue' 事件去更新 modelValue 属性,因此,组件的 template 中绑定到 pager 计算属性的元素会自动响应更新。

定义了一个名为 emit 的常量。

defineEmits() 函数用于类型安全地定义组件可以发出的事件。在此例中,defineEmits() 定义了两个事件:'change''update:modelValue',它们分别表示组件触发 change 事件和更新 modelValue 属性事件。

defineEmits() 函数接受一个泛型参数,它指定了所有定义的事件及其参数类型。在此例中,泛型参数指定了 change 事件没有参数、update:modelValue 事件有一个 value 参数且类型为 any

因此,当组件使用 emit() 方法触发这些事件时,TypeScript 或 Vue.js 会对事件名称和参数类型进行验证,从而增加了代码的可靠性和可维护性。

通过 withDefaults()defineProps() 函数定义了一个名为 props 的常量。

defineProps() 函数创建了一个新的 Props 类型的对象,其中 Props 表示一个 Vue.js 组件的属性(即 props)。

withDefaults() 函数接受两个参数:要合并默认值的对象和包含默认值的第二个对象。在此例中,withDefaults() 将默认的 modelValue 属性、默认的 pageSizes 数组和默认的 layout 字符串合并到 props 对象中。

modelValue 的默认值是一个返回空对象 {} 的函数,这意味着如果没有显式传入 modelValue 属性,它将默认设置为空对象。

同样,pageSizes 的默认值是一个返回数组 [20, 50, 100, 150] 的函数。这意味着如果没有显式传入 pageSizes 属性,它将默认设置为数组 [20, 50, 100, 150]

最后,layout 的默认值是一个字符串 'total, sizes, prev, pager, next, jumper',它是一个用逗号分隔的组成分页 UI 的组件列表。

usePaging pagination

Pagination 分页

withDefaults 是 Vue 3 中一个用于合并选项对象的实用函数。它可以接收一个默认选项对象和一个新选项对象,然后将它们合并成一个新的选项对象。

示例代码:

import { withDefaults } from 'vue';

const defaultOptions = {
  color: 'blue',
  fontSize: 14,
};

const options = withDefaults(defaultOptions, {
  color: 'red',
});

console.log(options); // 输出 { color: 'red', fontSize: 14 }

在上面的例子中,我们定义了一个名为 defaultOptions 的对象作为默认选项。然后我们调用 withDefaults 函数,将其作为第一个参数传递,并将一个新的选项对象作为第二个参数传递。由于新选项对象具有一个与默认选项对象相同的属性 color,因此它将覆盖默认选项对象中的该属性值。但是,由于新选项对象没有定义属性 fontSize,因此它将获取默认选项对象中的该属性。最终的选项对象将包含这两个属性,如示例代码中 console.log 打印的结果所示。

d700efe55b226eb5e2f77d1637772dd1.png
image.png

vue3国际化

npm i vue-i18n --save

npm i vue-i18n@next

新建zh-CN.js,存放中文,例如

const Settings = {
  settings: '设置',
  search: '搜索',
  airplaneMode: '飞行模式',
  wlan: '无线局域网',
  bluetooth: '蓝牙'
}

const Maps = {
  searchPlaceHolder: '搜索地点或地址'
}
// 按功能模块导出
export default {
  Settings,
  Maps
}

新建en-US.js,存放英文,例如

const Settings = {
  settings: 'Settings',
  search: 'Search',
  airplaneMode: 'Airplane Mode',
  wlan: 'WLAN',
  bluetooth: 'Bluetooth',
}

const Maps = {
  searchPlaceHolder: 'search'
}

export default {
  Settings,
  Maps
}
// 国际化多语言
import i18n from './locales/index'
app.use(i18n).mount('#app')

<div class="page-title">{{ $t("Settings.settings") }}</div>

<template>
  <button @click="changeLanguage">change language</button>
</template>

<script lang="ts">
import { defineComponent  } from "vue";
import { useI18n } from "vue-i18n";

export default defineComponent({
  name: "App",
  components: {},
  setup() {
    const { locale } = useI18n({ useScope: "global" });
    function changeLanguage() {
      locale.value = "en-US"; // 切换成英文
    }
    return {
      changeLanguage,
    };
  },
});
</script>

用setup语法糖

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖
解决Vue3.0中setup需要繁琐将声明的变量、函数以及 import 引入的内容通过return向外暴露,才能在<template/>使用的问题

<script setup>
//import引入的内容
import { getToday } from './utils'  
// 变量
const msg = 'Hello!'
// 函数
function log() {
  console.log(msg)
}
</script>
//在template中直接使用声明的变量、函数以及import引入的内容
<template>
  <div @click="log">{{ msg }}</div>
   <p>{{getToday()}}</p>
</template>

<script setup>语法糖里面的代码会被编译成组件 setup() 函数的内容,不需要通过return暴露 声明的变量、函数以及import引入的内容,即可在<template/>使用,并且不需要写export default{}

<script setup>语法糖里面的代码会被编译成组件 setup() 函数的内容。这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行

<script>
  console.log('script');//多次实例组件,只触发一次
  export default {
      setup() {
          console.log('setupFn');//每次实例化组件都触发和script-setup标签一样
      }
  }
  </script>

script-setup标签最终都会编译成setup() 函数的内容,每次实例化组件,就是实例化一次setup函数。script标签里面的setup函数也是一样每次实例化组件,就是实例化一次setup函数,但是script标签setup是需要写在export default{}内的,外的只是首次引入的时候执行一次

<script setup>引入组件将自动注册

<script setup>
import MyComponent from './MyComponent.vue'
//components:{MyComponent}  不需要注册直接使用
</script>
<template>
  <MyComponent />
</template>

组件通信:在<script setup>中必须使用 defineProps 和 defineEmits API 来替代 props 和 emits

defineProps 和 defineEmits具备完整的类型推断并且在 <script setup> 中是直接可用的

defineProps 代替props,接收父组件传递的数据(父组件向子组件传参)

<template>
  <div>父组件</div>
  <Child :title="msg" />
</template>
<script setup>
import {ref} from 'vue'
import Child from './child.vue'
const msg = ref('父的值')  //自动返回,在template直接解套使用
</script>

<template/> 中可以直接使用父组件传递的props

<script-setup> 需要通过props.xx获取父组件传递过来的props

<template>
  <div>子组件</div>
  <div>父组件传递的值:{{title}}</div>
</template>
<script setup>
//import {defineProps} from 'vue'   不需要引入
//语法糖必须使用defineProps替代props
const  props = defineProps({
  title: {
    type: String
  }
});
//script-setup 需要通过props.xx获取父组件传递过来的props
console.log(props.title) //父的值
</script>

defineEmit 代替emit,子组件向父组件传递数据(子组件向外暴露数据)

<template>
  <div>子组件</div>
  <button @click="toEmits">子组件向外暴露数据</button>
</template>
<script setup>
import {ref} from 'vue'
const name = ref('我是子组件')
//1、暴露内部数据
const  emits = defineEmits(['childFn']);
const  toEmits = () => {
  //2、触发父组件中暴露的childFn方法并携带数据
  emits('childFn',name)
}
</script>

父组件代码:

<template>
  <div>父组件</div>
  <Child  @childFn='childFn' />
  <p>接收子组件传递的数据{{childData}} </p>
</template>
<script setup>
import {ref} from 'vue'
import Child from './child.vue'
    
const childData = ref(null)    
const childFn=(e)=>{
    console.log('子组件触发了父组件childFn,并传递了参数e')
    childData=e.value
}    
       
</script>

<pagination v-model="pager" @change="getLists" />

<script setup>需主动向父组件暴露子组件属性 :defineExpose

使用 <script setup> 的组件,父组件是无法通过ref 或者 $parent 获取到子组件的ref等响应数据,需要通过defineExpose 主动暴露

<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
//主动暴露组件属性
defineExpose({
  a,
  b
})
</script>

父组件代码:

<template>
  <div>父组件</div>
  <Child  ref='childRef' />
  <button @click='getChildData'>通过ref获取子组件的属性 </button>
</template>
<script setup>
import {ref} from 'vue'
import Child from './child.vue'
const childRef= ref()  //注册响应数据  
const getChildData =()=>{
  //子组件接收暴露出来得值
  console.log(childRef.value.a) //1
  console.log(childRef.value.b) //2  响应式数据
}    
</script>

useSlots 和 useAttrs (少用,由于大部分人是SFC模式开发,在<template/>通过<slot/>标签就可以渲染插槽)

如果需要在script-setup中使用 slotsattrs 需要用useSlotsuseAttrs替代

需要引入:import { useSlots ,useAttrs } form 'vue'

<template/>中通过 $slots$attrs 来访问更方便(attrs用来获取父组件中非props的传递到子组件的参数/方法,attrs 用来获取父组件中非props的传递到子组件的参数/方法,attrs用来获取父组件中非props的传递到子组件的参数/方法,slots可以获取父组件中插槽传递的虚拟dom对象,在SFC模式应该用处不大,在JSX /TSX使用比较多)

父组件:

<template>
  <Child msg="非porps传值子组件用attrs接收" >
    <!-- 匿名插槽 -->
    <span >默认插槽</span>
    <!-- 具名插槽 -->
    <template #title>
      <h1>具名插槽</h1>
    </template>
    <!-- 作用域插槽 -->
    <template #footer="{ scope }">
      <footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
    </template>
  </Child>
</template>
<script setup>
// 引入子组件
import Child from './child.vue'
</script>

子组件:

<template>
  <!-- 匿名插槽 -->
  <slot />
  <!-- 具名插槽 -->
  <slot name="title" />
  <!-- 作用域插槽 -->
  <slot name="footer" :scope="state" />
  <!-- $attrs 用来获取父组件中非props的传递到子组件的参数 -->
  <p>{{ attrs.msg == $attrs.msg }}</p>
  <!--true  没想到有啥作用... -->
  <p>{{ slots == $slots }}</p>
</template>
<script setup>
import { useSlots, useAttrs, reactive, toRef } from 'vue'
const state = reactive({
  name: '张三',
  age: '18'
})
const slots = useSlots()
console.log(slots.default()); //获取到默认插槽的虚拟dom对象
console.log(slots.title());   //获取到具名title插槽的虚拟dom对象
// console.log(slots.footer()); //报错  不知道为啥有插槽作用域的无法获取
//useAttrs() 用来获取父组件传递的过来的属性数据的(也就是非 props 的属性值)。
const attrs = useAttrs()
</script>

useSlots 可以获取父组件传递过来插槽的虚拟dom对象,可以用来渲染插槽内容

<script lang='jsx'>
import { defineComponent, useSlots } from "vue";
export default defineComponent({
  setup() {
    // 获取插槽数据
    const slots = useSlots();
    // 渲染组件
    return () => (
      <div>
        {slots.default?slots.default():''}
        {slots.title?slots.title():''}
      </div>
    );
  },
});
</script>

访问路由实例组件信息:route和router

setup 里不能访问 this,不能再直接访问 this.$routerthis.$route。(getCurrentInstance可以替代this但不推荐)

推荐:使用useRoute 函数和useRouter函数替代this.$routethis.$router

<script setup>
import { useRouter, useRoute } from 'vue-router'
    const route = useRoute()
    const router = useRouter()
    
    function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
        },
      })
    }
  <script/>
import router from './router'
router.beforeEach((to,from,next)=>{

})

也可以使用组合式api的导航守卫onBeforeRouteLeave, onBeforeRouteUpdate

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

    // 与 beforeRouteLeave 相同,无法访问 `this`
    onBeforeRouteLeave((to, from) => {
      const answer = window.confirm(
        'Do you really want to leave? you have unsaved changes!'
      )
      // 取消导航并停留在同一页面上
      if (!answer) return false
    })

    const userData = ref()

    // 与 beforeRouteUpdate 相同,无法访问 `this`
    onBeforeRouteUpdate(async (to, from) => {
      //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
 <script/>

组合式 API 守卫也可以用在任何由 <router-view> 渲染的组件中,它们不必像组件内守卫那样直接用在路由组件上。

<template>
  <div class="watch-test">
    <div>ref定义数组:{{arrayRef}}</div>
    <div>reactive定义数组:{{arrayReactive}}</div>
  </div>
  <div>
    <button @click="changeArrayRef">改变ref定义数组第一项</button>
    <button @click="changeArrayReactive">改变reactive定义数组第一项</button>
  </div>
</template>

<script>
  import {ref, reactive, watch} from 'vue'

  export default {
    name: 'WatchTest',
    setup() {
      const arrayRef = ref([1, 2, 3, 4])
      const arrayReactive = reactive([1, 2, 3, 4])

      //ref not deep
      const arrayRefWatch = watch(arrayRef, (newValue, oldValue) => {
        console.log('newArrayRefWatch', newValue, 'oldArrayRefWatch', oldValue)
      })

      //ref deep
      const arrayRefDeepWatch = watch(arrayRef, (newValue, oldValue) => {
        console.log('newArrayRefDeepWatch', newValue, 'oldArrayRefDeepWatch', oldValue)
      }, {deep: true})

      //reactive,源不是函数
      const arrayReactiveWatch = watch(arrayReactive, (newValue, oldValue) => {
        console.log('newArrayReactiveWatch', newValue, 'oldArrayReactiveWatch', oldValue)
      })

      // 数组监听的最佳实践- reactive且源采用函数式返回,返回拷贝后的数据
      const arrayReactiveFuncWatch = watch(() => [...arrayReactive], (newValue, oldValue) => {
        console.log('newArrayReactiveFuncWatch', newValue, 'oldArrayReactiveFuncWatch', oldValue)
      })

      const changeArrayRef = () => {
        arrayRef.value[0] = 6
      }
      const changeArrayReactive = () => {
        arrayReactive[0] = 6
      }
      return {
        arrayRef,
        arrayReactive,
        changeArrayRef,
        changeArrayReactive
      }
    }
  }
</script>

国际化自动工具

vue 项目使用的是 [vue-i18n],而 react 项目使用 [react-i18next],这些插件原理都是根据前端提供的一套语言包json文件(例如en-US.json,zh-CN.json), 每份语言包都以key:value对象形式存储代码标记:实际渲染的语言文案一一对应的数据

3b8d441f6b8e0ce65056a2385c68f1a9.png
image.png
bac66e5a4f2cf4012fe968ddb489ab56.png
image.png

前端开发使用辅助插件 [@mango-scripts/i18n-scripts] 进行一键下载、导入语言包到业务系统本地代码

521fbc9215eb1711294cd126c2808a30.png
image.png
980422e711eb4cf2309e1e05d7875170.png
image.png

支持使用辅助插件 [@mango-scripts/i18n-scripts]进行一键下载、导入语言包到业务系统本地代码

文案解析器:[@mango-scripts/i18n-utils]基于 babel, vue-template-compiler, hyntax, pug, svelte/compiler 等 对目标源码进行 AST 解析

辅助插件:[@mango-scripts/i18n-scripts]基于 commander,fs-extra,glob,inquirer 等

https://github.com/AlbertLin0923/mango-i18n-system

https://github.com/AlbertLin0923/mango-scripts/tree/main/packages/i18n-scripts

Vue3的8种和Vue2的12种组件通信

// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2"></child>
<script>
import child from "./child.vue"
import { ref, reactive } from "vue"
export default {
    data(){
        return {
            msg1:"这是传级子组件的信息1"
        }
    },
    setup(){
        // 创建一个响应式数据
        
        // 写法一 适用于基础类型  ref 还有其他用处,下面章节有介绍
        const msg2 = ref("这是传级子组件的信息2")
        
        // 写法二 适用于复杂类型,如数组、对象
        const msg2 = reactive(["这是传级子组件的信息2"])
        
        return {
            msg2
        }
    }
}
</script>

// Child.vue 接收
<script>
export default {
  props: ["msg1", "msg2"],// 如果这行不写,下面就接收不到
  setup(props) {
    console.log(props) // { msg1:"这是传给子组件的信息1", msg2:"这是传给子组件的信息2" }
  },
}
</script>
// Parent.vue 传送
<child :msg2="msg2"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg2 = ref("这是传给子组件的信息2")
    // 或者复杂类型
    const msg2 = reactive(["这是传级子组件的信息2"])
</script>

// Child.vue 接收
<script setup>
    // 不需要引入 直接使用
    // import { defineProps } from "vue"
    const props = defineProps({
        // 写法一
        msg2: String
        // 写法二
        msg2:{
            type:String,
            default:""
        }
    })
    console.log(props) // { msg2:"这是传级子组件的信息2" }
</script>
// Child.vue 派发
<template>
    // 写法一
    <button @click="emit('myClick')">按钮</buttom>
    // 写法二
    <button @click="handleClick">按钮</buttom>
</template>
<script setup>
    
    // 方法一 适用于Vue3.2版本 不需要引入
    // import { defineEmits } from "vue"
    // 对应写法一
    const emit = defineEmits(["myClick","myClick2"])
    // 对应写法二
    const handleClick = ()=>{
        emit("myClick", "这是发送给父组件的信息")
    }
    
    // 方法二 不适用于 Vue3.2版本,该版本 useContext()已废弃
    import { useContext } from "vue"
    const { emit } = useContext()
    const handleClick = ()=>{
        emit("myClick", "这是发送给父组件的信息")
    }
</script>

// Parent.vue 响应
<template>
    <child @myClick="onMyClick"></child>
</template>
<script setup>
    import child from "./child.vue"
    const onMyClick = (msg) => {
        console.log(msg) // 这是父组件收到的信息
    }
</script>
// Child.vue
<script setup>
    // 方法一 不适用于Vue3.2版本,该版本 useContext()已废弃
    import { useContext } from "vue"
    const ctx = useContext()
    // 对外暴露属性方法等都可以
    ctx.expose({
        childName: "这是子组件的属性",
        someMethod(){
            console.log("这是子组件的方法")
        }
    })
    
    // 方法二 适用于Vue3.2版本, 不需要引入
    // import { defineExpose } from "vue"
    defineExpose({
        childName: "这是子组件的属性",
        someMethod(){
            console.log("这是子组件的方法")
        }
    })
</script>

// Parent.vue  注意 ref="comp"
<template>
    <child ref="comp"></child>
    <button @click="handlerClick">按钮</button>
</template>
<script setup>
    import child from "./child.vue"
    import { ref } from "vue"
    const comp = ref(null)
    const handlerClick = () => {
        console.log(comp.value.childName) // 获取子组件对外暴露的属性
        comp.value.someMethod() // 调用子组件对外暴露的方法
    }
</script>
// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2" title="3333"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg1 = ref("1111")
    const msg2 = ref("2222")
</script>

// Child.vue 接收
<script setup>
    import { defineProps, useContext, useAttrs } from "vue"
    // 3.2版本不需要引入 defineProps,直接用
    const props = defineProps({
        msg1: String
    })
    // 方法一 不适用于 Vue3.2版本,该版本 useContext()已废弃
    const ctx = useContext()
    // 如果没有用 props 接收 msg1 的话就是 { msg1: "1111", msg2:"2222", title: "3333" }
    console.log(ctx.attrs) // { msg2:"2222", title: "3333" }
    
    // 方法二 适用于 Vue3.2版本
    const attrs = useAttrs()
    console.log(attrs) // { msg2:"2222", title: "3333" }
</script>
// Parent.vue
<child v-model:key="key" v-model:value="value"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const key = ref("1111")
    const value = ref("2222")
</script>

// Child.vue
<template>
    <button @click="handlerClick">按钮</button>
</template>
<script setup>
    
    // 方法一  不适用于 Vue3.2版本,该版本 useContext()已废弃
    import { useContext } from "vue"
    const { emit } = useContext()
    
    // 方法二 适用于 Vue3.2版本,不需要引入
    // import { defineEmits } from "vue"
    const emit = defineEmits(["key","value"])
    
    // 用法
    const handlerClick = () => {
        emit("update:key", "新的key")
        emit("update:value", "新的value")
    }
</script>
// Parent.vue
<script setup>
    import { provide } from "vue"
    provide("name", "沐华")
</script>

// Child.vue
<script setup>
    import { inject } from "vue"
    const name = inject("name")
    console.log(name) // 沐华
</script>
// store/index.js
import { createStore } from "vuex"
export default createStore({
    state:{ count: 1 },
    getters:{
        getCount: state => state.count
    },
    mutations:{
        add(state){
            state.count++
        }
    }
})

// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")

// Page.vue
// 方法一 直接使用
<template>
    <div>{{ $store.state.count }}</div>
    <button @click="$store.commit('add')">按钮</button>
</template>

// 方法二 获取
<script setup>
    import { useStore, computed } from "vuex"
    const store = useStore()
    console.log(store.state.count) // 1

    const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
    console.log(count) // 1 
</script>

EventBus 跨组件通信

Vue3 中没有了 EventBus 跨组件通信

先安装 npm i mitt -S

mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt
// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
    mitt.emit('handleChange')
}
</script>

// 组件 B 
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
    mitt.off('handleChange',someMethed)
})
</script>

Vue2.x 组件通信共有12种

  1. props

  2. $emit / v-on

  3. .sync

  4. v-model

  5. ref

  6. parent

  7. listeners

  8. provide / inject

  9. EventBus

  10. Vuex

  11. $root

  12. slot

父子组件通信可以用:

  • props

  • $emit / v-on

  • listeners

  • ref

  • .sync

  • v-model

  • parent

兄弟组件通信可以用:

  • EventBus

  • Vuex

  • $parent

跨层级组件通信可以用:

  • provide/inject

  • EventBus

  • Vuex

  • listeners

  • $root

使用readonly包裹的数据,深层都是只读的,shallowReadonly只有最外面那层才是只读的

toRaw与markRaw

toRaw可以将由reactive或readonly函数转换成响应式代理的普通对象,对普通对象的属性值进行修改,就不会更新视图界面。一般用于渲染具有不可变数据源的大列表,跳过代理转换可以提高性能。

markRaw标记一个对象,使其永远不会转换为响应式数据,只能返回这个对象本身,一般用于某些值不该被设置为响应式的,比如第三方类实例或vue对象等场景。

markRaw标记一个永远不是响应式的数据, 哪怕后面用reactive转也是不响应式的

toRef为响应式对象上的某个属性创建一个Ref引用,更新时引用对象会同步更新,注意如果通过toRef创建的数据修改时,并不会触发视图界面的更新,因为toRef的本质是引用,与原始数据有关联。

从一个对象中拿出一个属性,操作这个属性

使用toRef使其成为响应式

let count =toRef(objA,'count')
          count.value++

加群联系作者vx:xiaoda0423

仓库地址:https://github.com/webVueBlog/WebGuideInterview

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/535038.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【004】C++数据类型之字符类型(char)详解

C数据类型之字符类型详解 引言一、背景知识二、字符常量三、字符变量四、键盘&#xff08;输入设备&#xff09;给字符变量赋值五、字符常量和字符串常量的区别六、案例&#xff1a;字符大小写转换总结 引言 &#x1f4a1; 作者简介&#xff1a;专注于C/C高性能程序设计和开发&…

知识变现海哥|为什么你无法将学到的知识变现

秋叶大叔在公众号里发了这样一篇文章《为什么今天的年轻人更要读大学&#xff1f;》&#xff0c;文章里提到这样一个观点&#xff1a;一个人的专业能力要获得优质的回报&#xff0c;还需要学一样「变现技能」。 我不由得想问一个问题&#xff1a;你真的理解变现能力吗&#xf…

微信公众号服务号客服对接-唯一客服系统文档中心

微信公众号客服功能&#xff0c;需要公众号为认证的服务号&#xff0c;可以实现公众号模板消息提醒&#xff0c;网页授权获取到微信的昵称头像&#xff0c;机器人或AI自动回复功能等 微信公众号后台设置 【设置与开发】【基本配置】 设置好公众号的AppID AppSecret IP白名单 【…

亚马逊云科技助力默沙东,用数字化技术赋能医药市场营销

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在全球药企中&#xff0c;默沙东是封神的存在。提及其明星药物&#xff0c;我们接种的乙肝疫苗、麻疹疫苗、宫颈癌九价疫苗、快速短效麻醉剂、抗生素青霉素钾等药物皆出自于默沙东。 2022年&#xff0c;全球药企TOP 10营业收…

ChatGPT国内免费使用方法有哪些?

目录 ChatGPT介绍:一、ChatGPT是什么?二、ChatGPT发展:三、ChatGPT 优点:四、国内使用ChatGPT方法五、结语: ChatGPT介绍: 一、ChatGPT是什么? ChatGPT 是一个基于语言模型 GPT-3.5 的聊天机器人&#xff0c;ChatGPT模型是Instruct GPT的姊妹模型&#xff08;siblingmodel&a…

Lighttpd服务器安装

目录 >1服务器安装配置 1.解压 2.进入源码目录&#xff0c;创建文件夹web 3.执行configure脚本文件 4.执行Makefile文件 >2目录创建及文件移动 1.将源码目录lighttpd-1.4.54下web文件夹移动到某个路径下 2.在web目录下创建文件夹&#xff08;config、log、run、www&#…

jsp+springboot酒店客房管理系统 i59uz

&#xff08;1&#xff09;管理员模块&#xff1a;系统记录管理员号以及密码&#xff0c;以及对权限的设置&#xff0c;对酒店进行信息管理&#xff0c;对房客进行信息的登记和修改&#xff0c;以及对酒店的信息进行维护。 Spring Boot 是 Spring 家族中的一个全新的框架&#…

【JVM】2. 类加载子系统

文章目录 2.1. 内存结构概述&#x1f352;2.2. 类加载器与类的加载过程&#x1f352;2.2.1 加载阶段&#x1f353;&#x1f353;2.2.2 链接阶段&#x1f353;&#x1f353;2.2.3 初始化阶段&#x1f353;&#x1f353; 2.3. 类加载器分类&#x1f352;2.3.1. 虚拟机自带的加载器…

基于自适应特征融合网络的地铁OD流预测

1 导读 本论文于2023年发表于交通领域顶刊《IEEE Transactions on Intelligent Transportation Systems》。文章针对OD流预测中存在的三点问题&#xff0c;即复杂动态的时空依赖性、数据稀疏性和不完整性以及容易受到外部因素影响展开研究&#xff0c;提出了一个自适应特征融合…

遇到一个同事,喜欢查其他同事的BUG,然后截图发工作大群里,还喜欢甩锅,该怎么办?...

职场上都有哪些奇葩同事&#xff1f; 一位网友吐槽&#xff1a; 遇到一个同事&#xff0c;喜欢查同级别同事的bug&#xff0c;截图发工作群&#xff0c;甚至发大群里&#xff0c;还喜欢甩锅&#xff0c;该怎么办&#xff1f; 职场工贼&#xff0c;人人喊打&#xff0c;网友们纷…

LCHub:网易数帆汪源:低代码仍然被“误会”,市场明年会迎拐点

LCHub:2023年四月底,在北京见到网易副总裁、网易杭州研究院执行院长、网易数帆总经理汪源,他展现出对于低代码、生成式AI技术非常开放的分享状态。汪源是网易杭州研究院执行院长,他负责的研究院在2006年已经成立,早期的网易数帆就是研究院的技术团队,支撑网易公司的数个主…

【MySQL聚合查询】轻松实现数据统计

1、聚合查询 聚合查询&#xff1a;行和行之间的运算 1.1 聚合函数 常见的聚合函数&#xff1a; count&#xff1a;数量 sum&#xff1a;求和 avg&#xff1a;求平均值 max&#xff1a;最大值 min&#xff1a;最小值 1.2 聚合函数的使用 1.2.1 count 函数 现在有一个 class 表&a…

【Python每日一练】总目录(2023.2.18~5.18)共90篇

2023.2.18~2023.5.18 连载三个月共90篇&#xff0c;暂停更 Python 2023.05 2023.05.11-2023.05.18 20230518 20230517 1. 最大连续1的个数 I Max Consecutive Ones &#x1f31f; 2. 最大连续1的个数 II Max Consecutive Ones &#x1f31f;&#x1f31f; 3. 最大连续1的个…

什么是K-近邻算法?

一、什么是K-近邻算法&#xff1f; K近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基本的分类和回归算法。它的基本原理是通过测量样本之间的距离来对未知样本进行分类或预测。KNN算法被归类为一种“懒惰学习”&#xff08;lazy learning&am…

【K8s】安全认证与DashBoard

文章目录 一、概述1、客户端2、认证、鉴权与准入控制 二、认证管理1、认证方式2、HTTPS证书认证 三、授权管理1、授权与RBAC2、Role 与 ClusterRole3、RoleBinding 与 ClusterRoleBinding4、案例&#xff1a;创建一个只能管理dev空间下Pods资源的账号 四、准入控制五、DashBoar…

【ARMv8 编程】A64 流控制指令

A64 指令集提供了许多不同种类的分支指令。对于简单的相对分支&#xff0c;即那些从当前地址偏移的分支&#xff0c;使用 B 指令。无条件简单相对分支可以从当前程序计数器位置向后或向前分支最多 128MB。 有条件的简单相对分支&#xff0c;其中条件代码附加到 B&#xff0c;具…

用Pin对二进制文件自动脱壳

Intel Pin Intel Pin在可执行二进制代码中插入一些探测函数,用于观察、记录、分析宿主代码执行过程中的一些与计算机体系结构相关的特性,如访存指令,寄存器内容,寄存器地址等,通过Pin提供的API可以编写各种分析函数,这样程序运行完以后,统计和分析结果也同时产生,分析…

solidworks2020及麦迪工具箱安装

1、麦迪工具箱安装 1&#xff09;下载 下载链接&#xff1a;www.maidiyun.com/download 下载今日制造 2&#xff09;安装 由于电脑上安装了杀毒软件&#xff0c;会直接删除解压后的安装包&#xff0c;因此需要关闭杀毒软件或者在被删除后进入杀毒软件的隔离区将该文件添加…

网站信息收集

网站信息收集 ip信息收集 域名Whois查询 - 站长之家 Whois查询 http://tool.chinaz.com/ 站长工具 https://dns.aizhan.com/ 爱站网 ping检测 ip反查域 https://x.threatbook.cn/ 微步在线 https://toolbar.netcraft.com/site_report 网站查询 http://tool.chinaz.com/n…

JECloud 运行前端模块所遇到的问题(持续学习汇总)

1.依赖安装问题 官网 JECLoud 前端打包部署 1.1 部署前 注意 可以先下载 jecloud-libs 项目 尝试 部署安装 node版本不能过高&#xff01;选择node v14.17.5 版本 在部署前 如果当前node版本过高可以使用 nvm工具 切换到低版本&#xff0c; 但是需要注意的是 切换版本时 …