前端系列16集-vue3范型,vue-i18n-next,watch,watchEffect

2023-10-08 19:10:49 浏览数 (2)

vue-i18n-next

vue3 中使用 i18n 需要安装的是 [vue-i18n v9] 的版本

npm install vue-i18n@9

创建 srclangindex.ts,使用 createI18n 创建 i18n 实例:

代码语言:javascript复制
// srclangindex.ts
    import { createI18n } from 'vue-i18n'
    import { LANG_VALUE } from '@/common/enum'
    import ch from './ch'
    import en from './en'

    const i18n = createI18n({
      legacy: false,
      locale: getLanguage(),
      messages: {
        [LANG_VALUE.ch]: ch,
        [LANG_VALUE.En]: en
      }
    })
    export default i18n

如果使用的是 Composition API 则需要定义为 false

  • locale:当前要展示的语言。值为之前用户的语言选择,从浏览器缓存中读取。如果缓存中没有数据,则通过 navigator.language 获取浏览器使用的语言:
代码语言:javascript复制
// srclangindex.ts
import { localCache } from '@/utils'
export function getLanguage() {
  const chooseLanguage = localCache.getItem(LANGUAGE)
  if (chooseLanguage) return chooseLanguage

  // 如果没有选择语言
  const language = navigator.language.toLowerCase()
  const locales = [LANG_VALUE.En, LANG_VALUE.Zh]
  for (const locale of locales) {
    if (language.indexOf(locale) > -1) {
      return locale
    }
  }
  return LANG_VALUE.Zh
}
代码语言:javascript复制
// srccommonenum.ts
export enum LANG_VALUE {
  En = 'en',
  Zh = 'zh-Hans'
}
  • messages:不同语言对应的翻译文件:
代码语言:javascript复制
// srclangzh-Hans.ts
export default {
  baoguochuku: '包裹出库',
  sousuo: '搜索'
}
代码语言:javascript复制
// srclangen.ts
export default {
  baoguochuku: 'Outbound',
  sousuo: 'Search'
}
代码语言:javascript复制
// srcmain.ts,省略其它代码
import i18n from './lang/index'
app.use(i18n)

从 vue-i18n 中导入 useI18n,然后进行调用生成 i18n 实例,再从里面解构得到 t 方法

  • 在 <template> 中使用
代码语言:javascript复制
<template>
  <n-button>
    <slot name="submitBtnText">{{ $t('xxx') }}</slot>
  </n-button>
</template>

直接使用 t 是因为有个叫做 globalInjection 的配置项默认为 true,帮我们全局注入了 t 方法,如果设置为 false:

代码语言:javascript复制
// srclangindex.ts
const i18n = createI18n({
  globalInjection: false
})

在 ts 文件中,在 srclangindex.ts 配置生成的 i18n 实例

代码语言:javascript复制
// srcservicerequestindex.ts
import i18n from '@/lang'
if (!axios.isCancel(error)) {
  dialog.error({
    title: i18n.global.t('biaoti'),
    // ...
  })
}
代码语言:javascript复制
import { watch, ref } from 'vue'
 setup() {
      let mes = ref('会好的')
      //第一种情况 监听ref定义的一个响应式数据
      watch(mes, (qian, hou) => {
        console.log('变化----', qian, hou)
      }, {immediate: true})
 }

 setup() {
      let mes = ref('会好的')
      let mess = ref('我是谁')
      //第二种情况 监听ref定义的多个响应式数据
      watch([mes,mess], (qian, hou) => {
        console.log('变化----', qian, hou)
      },{immediate:true})
 }

setup() {
       let rea=reactive({
        name:'我是谁',
        obj:{
          salary:20
        }
      })
      //第三种情况 监听reactive定义的属性
       watch(rea,(qian,hou)=>{
        console.log('rea变化了',qian,hou)
      })
 }
停止监听
代码语言:javascript复制
const stop = watchEffect(() => {
  /* ... */
})

// later
stop()
代码语言:javascript复制
const data = ref(null)
watchEffect(async onInvalidate => {
  onInvalidate(() => {
    /* ... */
  }) // 我们在Promise解析之前注册清除函数
  data.value = await fetchData(props.id)
})
  1. watch可以访问新值和旧值,watchEffect不能访问。
  2. watchEffect有副作用,DOM挂载或者更新之前就会触发,需要我们自己去清除副作用。
  3. watch是惰性执行,也就是只有监听的值发生变化的时候才会执行,但是watchEffect不同,每次代码加载watchEffect都会执行。
  4. watch需要指明监听的对象,也需要指明监听的回调。watchEffect不用指明监视哪一个属性,监视的回调函数中用到哪个属性,就监视哪个属性。
  • 推荐使用vscode,并且安装volar插件辅助开发
代码语言:javascript复制
<template>
    <div>{{ numberRef }}</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const numberRef = ref(0) // ===> Ref<number>
const stringRef = ref("") // ===> Ref<string>
interface IFoo {
    bar: string
}
const fooRef = ref<IFoo>() // ===> Ref<IFoo | undefined>
</script>
代码语言:javascript复制
<template>
    <div>{{ book1.bar }}</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
interface IFoo {
    bar: string
}
// 第一种
const book1 = reactive<IFoo>({ bar: 'bar' })
// 第二种
const book2: IFoo = reactive({ bar: 'bar' })
// 第三种
const book3 = reactive({ bar: 'bar' }) as IFoo
</script>
代码语言:javascript复制
<template>
    <div>{{ fooComputed?.bar }}</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const numberRef = ref(0) // ===> Ref<number>
const numberComputed = computed(() => numberRef.value) // ===> ComputedRef<number>
interface IFoo {
    bar: string
}
const fooRef = ref<IFoo>() // ===> Ref<IFoo | undefined>
const fooComputed = computed(() => {
    return fooRef.value
}) // ===> ComputedRef<IFoo | undefined>
</script>
代码语言:javascript复制
<template>
    <div>{{ fooComputedWritable?.bar }}</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
interface IFoo {
    bar: string
}
const fooRef = ref<IFoo>() // ===> Ref<IFoo | undefined>
const fooComputedWritable = computed({
    get: () => {
        return fooRef.value
    },
    set: (value) => {
        fooRef.value = value
    }
}) // ===> WritableComputedRef<IFoo | undefined>
</script>
代码语言:javascript复制
<template>
    <div>{{ numberRef }}</div>
</template>
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue';
const numberRef = ref(0) // ===> Ref<number>
interface IFoo {
    bar: string
}
const fooRef = ref<IFoo>() // ===> Ref<IFoo | undefined>

watchEffect(() => console.log(fooRef.value?.bar))
watch(numberRef, () => {
    console.log(`numberRef变化了`)
})
const stop = watch(fooRef, () => {
    console.log(`fooRef变化了`)
}, {
    deep: true
}) // 检查深度嵌套的对象或数组
stop(); // 停止侦听
</script>
代码语言:javascript复制
<template>
    <div></div>
</template>
<script setup lang="ts">
import { nextTick } from 'vue';

nextTick(() => {
    // ...
})

// 还可以使用 async/await
async () => {
    await nextTick()
    // ....
}
</script>
代码语言:javascript复制
<template>
    <Foo></Foo>
    <foo-item></foo-item>
    <component :is="Foo" />
    <component :is="someCondition ? Foo : FooItem" />
</template>
<script setup lang="ts">
import Foo from "./Foo.vue"
import FooItem from "./FooItem.vue"
const someCondition = false
</script>
代码语言:javascript复制
<template>
    <div>
        <!-- 直接使用即可,不需要toRefs转换 -->
        {{ foo }}
    </div>
</template>
<script setup lang="ts">
import { toRefs } from 'vue';
interface ICustomType {
    foo: string,
    bar: string
}
const props = defineProps({
    foo: String, // 使用构造函数声明类型
    fooMultiTypes: [String, Number], // 多个类型
    fooCustomType: Object as () => ICustomType, // 自定义类型
    fooCustomTypeWithRequire: {
        type: Object as () => ICustomType,
        required: true
    }, // 自定义类型,必选
    fooCustomTypeWithDefault: {
        type: Object as () => ICustomType,
        default: () => {
            return {
                foo: "foo",
                bar: "bar"
            }
        }
    }, // 自定义类型,带默认值
})

// 1. 可以在模板<template>中使用声明的props,不需要用toRefs转换
// 2. 如果某一个值需要在setup中使用,则需要用toRefs转换下,然后把它解构出来
const {
    foo,  // ===> Ref<string | undefined> | undefined
    fooMultiTypes, // ===> Ref<string | number | undefined> | undefined
    fooCustomType, // ===> Ref<ICustomType | undefined> | undefined
    fooCustomTypeWithRequire, // ===> Ref<ICustomType>
} = toRefs(props)
</script>
代码语言:javascript复制
<template>
    <div>
        <!-- 直接使用即可,不需要toRefs转换 -->
        {{ foo }}
    </div>
</template>
<script setup lang="ts">
import { toRefs } from 'vue';
interface ICustomType {
    foo: string,
    bar: string
}
const props = defineProps<{
    foo?: string,
    fooWithRequire: string,
    fooMultiTypes: string | number,
    fooCustomType?: ICustomType,
    fooCustomTypeWithRequire: ICustomType
}>()

// 泛型方式声明默认值,需要使用withDefaults 编译器宏
const propsWithDefault = withDefaults(
    defineProps<{
        fooCustomTypeWithDefault: ICustomType
    }>(),
    {
        fooCustomTypeWithDefault: () => {
            return {
                foo: "foo",
                bar: "bar"
            }
        }
    })

// 1. 可以在模板<template>中使用声明的props,不需要用toRefs转换
// 2. 如果某一个值需要在setup中使用,则需要用toRefs转换下,然后把它解构出来
const {
    foo,  // ===> Ref<string | undefined> | undefined
    fooWithRequire,  // ===> Ref<string>
    fooMultiTypes, // ===> Ref<string | number>
    fooCustomType, // ===> Ref<ICustomType | undefined> | undefined
    fooCustomTypeWithRequire, // ===> Ref<ICustomType>
} = toRefs(props)

const {
    fooCustomTypeWithDefault,  // ===> Ref<ICustomType>
} = toRefs(propsWithDefault)
</script>
代码语言:javascript复制
// FooBar.vue
<template>
    <div>
        <!-- 一个单文件组件可以通过它的文件名被其自己所引用 -->
        <FooBar></FooBar>
        <foo-bar></foo-bar>

        <foo-bar-other></foo-bar-other>
    </div>
</template>
<script setup lang="ts">
// 使用 import 别名导入避免冲突
import { default as FooBarOther } from './others/FooBar.vue'
</script>
代码语言:javascript复制
<template>
    <div></div>
</template>
<script setup lang="ts">
import {
    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    onBeforeUnmount,
    onUnmounted,
    onErrorCaptured,
    onRenderTracked,
    onRenderTriggered,
    onActivated,
    onDeactivated
} from "vue"

// 直接使用就好了
onMounted(() => {
    // ...
})
</script>
代码语言:javascript复制
<template>
    <div></div>
</template>
<script setup lang="ts">
import { provide } from 'vue';
export interface IUser {
    name: string,
    age: number
}
provide("name", "foo")
provide<IUser>("user", {
    name: "foo",
    age: 23
})
</script>
代码语言:javascript复制
<template>
    <div></div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { IUser } from './Foo.vue';

const name = inject<string>("name") // ===> string | undefined
const user = inject<IUser>("user") // ===> IUser | undefined
</script>
代码语言:javascript复制
import { createApp } from 'vue';
import App from './App.vue'

createApp(App).mount('#app')

利用了 import 和 export 的导入导出语法,实现了按需打包模块的功能,项目打包后的文件体积明显小了很多

代码语言:javascript复制
<template>
  <div id="app">
      <p>{{ number }}</p>
      <button @click="add">增加</button>
  </div>
</template>

<script>
// 1. 从 vue 中引入 ref 函数
import {ref} from 'vue'
export default {
  name: 'App',
  setup() {
      // 2. 用 ref 函数包装一个响应式变量 number
      let number = ref(0)

      // 3. 设定一个方法
      function add() {
          // number是被ref函数包装过了的,其值保存在.value中
          number.value   
      }

      // 4. 将 number 和 add 返回出去,供template中使用
      return {number, add}
  }
  
}
</script>

setup 函数还有两个参数,分别是 props 、context,前者存储着定义当前组件允许外界传递过来的参数名称以及对应的值;后者是一个上下文对象,能从中访问到 attr 、emit 、slots

代码语言:javascript复制
<template>
  <div id="app"></div>
</template>

<script>
// 1. 从 vue 中引入 多个生命周期函数
import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'
export default {
  name: 'App',
  setup() {
      onBeforeMount(() => {
          // 在挂载前执行某些代码
      })

      onMounted(() => {
          // 在挂载后执行某些代码
      })

      onBeforeUpdate(() => {
          // 在更新前前执行某些代码
      })

      onUpdated(() => {
          // 在更新后执行某些代码
      })

      onBeforeUnmount(() => {
          // 在组件销毁前执行某些代码
      })

      unMounted(() => {
          // 在组件销毁后执行某些代码
      })

      return {}
  }
  
}
</script>

reactive 方法是用来创建一个响应式的数据对象,该API也很好地解决了Vue2通过 defineProperty 实现数据响应式的缺陷

代码语言:javascript复制
<template>
  <div id="app">
   <!-- 4. 访问响应式数据对象中的 count  -->
   {{ state.count }}
  </div>
</template>

<script>
// 1. 从 vue 中导入 reactive 
import {reactive} from 'vue'
export default {
  name: 'App',
  setup() {
      // 2. 创建响应式的数据对象
      const state = reactive({count: 3})

      // 3. 将响应式数据对象state return 出去,供template使用
      return {state}
  }
}
</script>

.value 是在 setup 函数中访问 ref 包装后的对象时才需要加的,在 template 模板中访问时是不需要的,因为在编译时,会自动识别其是否为 ref 包装过的

代码语言:javascript复制
<script>
// 1. 导入 toRef
import {toRef} from 'vue'
export default {
    setup() {
        const obj = {count: 3}
        // 2. 将 obj 对象中属性count的值转化为响应式数据
        const state = toRef(obj, 'count')

        // 3. 将toRef包装过的数据对象返回供template使用
        return {state}
    }
}
</script>
代码语言:javascript复制
<script>
// 1. 导入 ref
import {ref} from 'vue'
export default {
    setup() {
        const obj = {count: 3}
        // 2. 将 obj 对象中属性count的值转化为响应式数据
        const state = ref(obj.count)

        // 3. 将ref包装过的数据对象返回供template使用
        return {state}
    }
}
</script>
代码语言:javascript复制
<template>
    <p>{{ state1 }}</p>
    <button @click="add1">增加</button>

 <p>{{ state2 }}</p>
    <button @click="add2">增加</button>
</template>

<script>
import {ref, toRef} from 'vue'
export default {
    setup() {
        const obj = {count: 3}
        const state1 = ref(obj.count)
        const state2 = toRef(obj, 'count')

        function add1() {
            state1.value   
            console.log('原始值:', obj);
            console.log('响应式数据对象:', state1);
        }

        function add2() {
            state2.value   
            console.log('原始值:', obj);
            console.log('响应式数据对象:', state2);
        }

        return {state1, state2, add1, add2}
    }
}
</script>

ref 是对原数据的一个拷贝,不会影响到原始值,同时响应式数据对象值改变后会同步更新视图

视图未发生改变,原始值改变了,响应式数据对象的值也改变了,这说明 toRef 是对原数据的一个引用,会影响到原始值,但是响应式数据对象值改变后不会更新视图

  1. ref 是对传入数据的拷贝;toRef 是对传入数据的引用
  2. ref 的值改变会更新视图;toRef 的值改变不会更新视图

toRefs ,其作用就是将传入的对象里所有的属性的值都转化为响应式数据对象,该函数支持一个参数,即 obj 对象

将 obj 作为参数传递给 reactive 生成响应式数据对象时,若 obj 的层级不止一层,那么会将每一层都用 Proxy 包装一次

shallowReactive

一个浅层的 reactive

代码语言:javascript复制
<template>
 <p>{{ state.a }}</p>
 <p>{{ state.first.b }}</p>
 <p>{{ state.first.second.c }}</p>
 <button @click="change1">改变1</button>
 <button @click="change2">改变2</button>
</template>
<script>
import {shallowReactive} from 'vue'
export default {
    setup() {
        const obj = {
            a: 1,
            first: {
                b: 2,
                second: {
                    c: 3
                }
            }
        }

        const state = shallowReactive(obj)

        function change1() {
            state.a = 7
        }

        function change2() {
            state.first.b = 8
            state.first.second.c = 9
            console.log(state);
        }

        return {state}
    }
}
</script>

改变了第二层的 b 和第三层的 c,虽然值发生了改变,但是视图却没有进行更新;改变了第一层的 a 时,整个视图进行了更新;shallowReactive 监听了第一层属性的值,一旦发生改变,则更新视图

shallowRef

一个浅层的 ref,与 shallowReactive 一样是拿来做性能优化的

shallowReactive 是监听对象第一层的数据变化用于驱动视图更新,那么 shallowRef 则是监听 .value 的值的变化来更新视图的

代码语言:javascript复制
<template>
 <p>{{ state.a }}</p>
 <p>{{ state.first.b }}</p>
 <p>{{ state.first.second.c }}</p>
 <button @click="change1">改变1</button>
 <button @click="change2">改变2</button>
</template>

<script>
import {shallowRef} from 'vue'
export default {
    setup() {
        const obj = {
            a: 1,
            first: {
                b: 2,
                second: {
                    c: 3
                }
            }
        }

        const state = shallowRef(obj)
        console.log(state);

        function change1() {
            // 直接将state.value重新赋值
            state.value = {
                a: 7,
                first: {
                    b: 8,
                    second: {
                        c: 9
                    }
                }
            }
        }

        function change2() {
            state.value.first.b = 8
            state.value.first.second.c = 9
            console.log(state);
        }

        return {state, change1, change2}
    }
}
</script>

triggerRef ,调用它就可以立马更新视图,其接收一个参数 state ,即需要更新的 ref 对象

代码语言:javascript复制
<template>
 <p>{{ state.a }}</p>
 <p>{{ state.first.b }}</p>
 <p>{{ state.first.second.c }}</p>
 <button @click="change">改变</button>
</template>

<script>
import {shallowRef, triggerRef} from 'vue'
export default {
    setup() {
        const obj = {
            a: 1,
            first: {
                b: 2,
                second: {
                    c: 3
                }
            }
        }

        const state = shallowRef(obj)
        console.log(state);

        function change() {
            state.value.first.b = 8
            state.value.first.second.c = 9
            // 修改值后立即驱动视图更新
            triggerRef(state)
            console.log(state);
        }

        return {state, change}
    }
}
</script>

toRaw 方法是用于获取 ref 或 reactive 对象的原始数据的

代码语言:javascript复制
<template>
 <p>{{ state.name }}</p>
 <p>{{ state.age }}</p>
 <button @click="change">改变</button>
</template>

<script>
import {reactive} from 'vue'
export default {
    setup() {
        const obj = {
            name: 'xxx',
            age: 22
        }

        const state = reactive(obj) 

        function change() {
            state.age = 90
            console.log(obj); // 打印原始数据obj
            console.log(state);  // 打印 reactive对象
        }

        return {state, change}
    }
}
</script>

toRaw 方法从 reactive 对象中获取到的是原始数据

可以很方便的通过修改原始数据的值而不更新视图来做一些性能优化了

代码语言:javascript复制
<script>
import {reactive, toRaw} from 'vue'
export default {
    setup() {
        const obj = {
            name: 'xxx',
            age: 22
        }

        const state = reactive(obj) 
        const raw = toRaw(state)

        console.log(obj === raw)   // true
    }
}
</script>

markRaw 方法可以将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据

代码语言:javascript复制
<template>
 <p>{{ state.name }}</p>
 <p>{{ state.age }}</p>
 <button @click="change">改变</button>
</template>

<script>
import {reactive, markRaw} from 'vue'
export default {
    setup() {
        const obj = {
            name: 'xxx',
            age: 22
        }
        // 通过markRaw标记原始数据obj, 使其数据更新不再被追踪
        const raw = markRaw(obj)   
        // 试图用reactive包装raw, 使其变成响应式数据
        const state = reactive(raw) 

        function change() {
            state.age = 90
            console.log(state);
        }

        return {state, change}
    }
}
</script>

被 markRaw 方法处理过后的数据是否还能被 reactive 包装成响应式数据

watch 和 watchEffect 都是用来监视某项数据变化从而执行指定的操作的

代码语言:javascript复制
<script>
import {ref, watch} from 'vue'
export default {
    setup() { 
        const state = ref(0)

        watch(state, (newValue, oldValue) => {
            console.log(`原值为${oldValue}`)
            console.log(`新值为${newValue}`)
            /* 1秒后打印结果:
                            原值为0
                            新值为1
            */
        })

        // 1秒后将state值 1
        setTimeout(() => {
            state.value   
        }, 1000)
    }
}
</script>
代码语言:javascript复制
<script>
import {reactive, watch} from 'vue'
export default {
    setup() { 
        const state = reactive({count: 0})

        watch(() => state.count, (newValue, oldValue) => {
            console.log(`原值为${oldValue}`)
            console.log(`新值为${newValue}`)
            /* 1秒后打印结果:
                            原值为0
                            新值为1
            */
        })

        // 1秒后将state.count的值 1
        setTimeout(() => {
            state.count   
        }, 1000)
    }
}
</script>
代码语言:javascript复制
<script>
import {reactive, watch} from 'vue'
export default {
    setup() { 
        const state = reactive({ count: 0, name: 'xx' })

        watch(
            [() => state.count, () => state.name], 
            ([newCount, newName], [oldvCount, oldvName]) => {
                console.log(oldvCount) // 旧的 count 值
                console.log(newCount) // 新的 count 值
                console.log(oldName) // 旧的 name 值
                console.log(newvName) // 新的 name 值
            }
        )

        setTimeout(() => {
          state.count   
          state.name = 'dd'
        }, 1000)
    }
}
</script>

watch方法会返回一个stop方法,若想要停止监听,便可直接执行该stop函数

watchEffect

  1. 不需要手动传入依赖
  2. 每次初始化时会执行一次回调函数来自动获取依赖
  3. 无法获取到原值,只能得到变化后的值
代码语言:javascript复制
<script>
import {reactive, watchEffect} from 'vue'
export default {
    setup() { 
        const state = reactive({ count: 0, name: 'xx' })

        watchEffect(() => {
            console.log(state.count)
            console.log(state.name)
            /*  初始化时打印:
                            0
                            xx

                1秒后打印:
                            1
                            da
            */
        })

        setTimeout(() => {
          state.count   
          state.name = 'da'
        }, 1000)
    }
}
</script>

const store = useStore()

Vue3的 getCurrentInstance().ctx

代码语言:javascript复制
<template>
  <div>
    <div ref="el">div元素</div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
export default {
  setup() {
      // 创建一个DOM引用,名称必须与元素的ref属性名相同
      const el = ref(null)

      // 在挂载后才能通过 el 获取到目标元素
      onMounted(() => {
        el.value.innerHTML = '内容被修改'
      })

      // 把创建的引用 return 出去
      return {el}
  }
}
</script>

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

0 人点赞