046Vue3的官方推荐的三个组件传值解决方案:props、pinia(状态管理)、provide和inject

  • 以下代码基于setup编写

props

<script setup>
const props = defineProps(['foo'])
// 或者 用下面的方式也可以
const props = defineProps({
  foo: String
})

// js中使用方法props.foo,注意:前面加上props
console.log(props.foo)
</script>

<template>
  <!--在html中直接调用foo即可,如果js中不需要使用props可以直接defineProps(['foo'])-->
  <div>{{ foo }}</div>
</template>

props单向数据流

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
  • prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter'])

// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
  • 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size'])

// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())

Prop 校验

defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // Number 类型的默认值
  propD: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propE: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})

pinia(状态管理)

import { defineStore } from 'pinia'

export const useTodos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // 类型将自动推断为 number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // 自动补全! ✨
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // 调用其他带有自动补全的 getters ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // 接受任何数量的参数,返回一个 Promise 或不返回
    addTodo(text) {
      // 你可以直接变更该状态
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

pinia的封装与引入

import { toStr } from '@/utils/Json';
import { createPinia, type PiniaPluginContext } from 'pinia'; // 引入pinia
import { toRaw } from 'vue';
const pinia = createPinia(); // 创建

// 定义缓存管理用的配置数据类型
interface IStorageOption {
  key: string;
  keeps: {
    sessions: string[];
    locals: string[];
  };
}
// 缓存配置数据
const storageOption: IStorageOption = {
  key: '__pinia__', //缓存名key的前置字符串
  keeps: {
    sessions: [], // 采用seesionStorage缓存的key
    locals: ['model', 'model00411'], // 采用localStorage缓存的key
  },
};
// 储存pinia的值
function setStorage(
  key: string,
  val: any,
  storageType: Storage = localStorage
) {
  const obj = { v: val };
  console.log(666.789, key);
  storageType.setItem(storageOption.key + key, toStr(obj));
}
// 从缓存中获取pinia的值
function getStorage(key: string, storageType: Storage = localStorage) {
  const val = storageType.getItem(storageOption.key + key);
  const obj = val ? JSON.parse(val as string) : null;
  return obj?.v || null;
}
// 依据配置项综合处理缓存的读写
function doStorage(
  key: string,
  store: any,
  storageType: Storage = localStorage
) {
  const storageResult = getStorage(key, storageType);
  if (storageResult) {
    console.log(666.7007, '读取啦', storageResult, key, toRaw(store.$state));
    store.$patch(() => {
      store.$state = { ...storageResult };
    });
  }
  store.$subscribe(() => {
    console.log(666.7002, '更新啦', key, toRaw(store.$state));
    setStorage(key, toRaw(store.$state), storageType);
  });
}
// 处理pinia缓存需要的中间件
function useStorage() {
  return (ctx: PiniaPluginContext) => {
    const { store } = ctx;
    const sid = store.$id;
    if (storageOption.keeps.locals.includes(sid)) {
      doStorage(sid, store, localStorage);
    } else if (storageOption.keeps.sessions.includes(sid)) {
      doStorage(sid, store, sessionStorage);
    }
  };
}

pinia.use(useStorage());

export default pinia;
引入
import { createApp } from 'vue';
import App from './App.vue';
import pinia from './stores';

const app = createApp(App);
app.use(pinia);
app.mount('#app');
项目中使用方法
import { defineStore } from 'pinia';

// 默认的初始化数据引入
const modelStoreTmp = {
  type: 1,
  data: {
    val: 3,
    item: { a: 1, b: 2 },
  },
};

export default defineStore('piniaName', {
  state: () => {
    return {
      modelStore: modelStoreTmp,
    };
  },
  actions: {},
});
  • 调用方法
import ModelStore from './store';
// 通过状态管理,得到一个ref的值
const modelStore = ModelStore().modelStore;

provide和inject

Provide (提供)

import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

Inject (注入)

import { inject } from 'vue'

// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const message = inject('message', '这是默认值')

建议尽可能将任何对响应式状态的变更都保持在供给方组件中

import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
  • 注入调用方式
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

三种方式的传值,按需使用

  • 父子组件使用props
  • 有层级关系的多个组件provide
  • 跨无关联组件使用pinia
Logo

前往低代码交流专区

更多推荐