一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

二十四、项目实战细节(二)

1、组件设置name属性
<script setup>
// Vue3.3后支持
defineOptions({
  name: '组件名',
  inheritAttrs: false
})
</script>
2、$slots和$scopedSlots
<template>
  <abc>
    <div>默认插槽</div>
    <template #aaa>
      <div>具名插槽</div>
    </template>
    <template #bbb="scope">
      <div>作用域插槽{{ scope }}</div>
    </template>
  </abc>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import Abc from '@/views/login/Abc.vue'

export default defineComponent({
  name: 'Login',
  components: {
    Abc
  }
})
</script>
<script lang="ts">
import { defineComponent, CreateElement, VNode } from 'vue'

export default defineComponent({
  name: 'Abc',
  render(createElement: CreateElement): VNode {
    console.log(this.$slots) // 不包含作用域插槽
    console.log(this.$scopedSlots) // 可以传值
    // children数组参数会自动flat(Infinity)
    return createElement('div', {}, [
      this.$slots.default,
      this.$slots.aaa,
      this.$scopedSlots!.default!(null),
      this.$scopedSlots!.aaa!(null),
      this.$scopedSlots!.bbb!('bbb')
    ])
  }
})
</script>
3、vue模版的for-in和for-of
<template>
  <div>
    <div v-for="(item,index) of arr" :key="index">{{ item }}</div>
    <div v-for="(value,key) in obj" :key="key">{{ key }}: {{ value }}</div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Abc',
  data() {
    return {
      arr: ['猛犸', '斧王', '马尔斯'],
      obj: {
        name: '冰女',
        kill: 10,
        dead: 0
      }
    }
  }
})
</script>

二十五、pinia

1、main.ts
import App from './App.vue'
import router from './router'
import { createApp } from 'vue'
// 1. 导入创建 pinia 实例的方法
import { createPinia } from 'pinia'

const app = createApp(App)
// 2. 创建pinia实例,并挂载到app上
app.use(createPinia())

app.use(router)

app.mount('#app')
2、store目录
import { defineStore } from 'pinia';

/**
 * 定义一个Store: state、getters、actions
 * 
 *  通过 defineStore 方法定义Store(全局状态数据),方法接收两个参数:
 *  参数1:应用中Store的唯一标识字符串id,不可重复
 *  参数2:接收Option对象或Setup函数形式
 *  返回值:返回的是一个Store对象
 *      返回值变量名可以任意命名,但是官方推荐使用 Store 结尾,同时以use开头
 *     使用 use 开关是符合一个组合式函数风格的约定,
 *      比如:useCartStore,useLoginStore,useUserStore
 */
// 参数2为函数形式
// defineStore("cart", () => {});

// 参数2为对象形式
interface Goods {
    id: number;
    name: string;
    num: number;
}
interface CartState {
    name: string;
    age: number;
    goodsList: Goods[];
}

export const useCartStore = defineStore("cart", {
    // state 用于定义一个返回初始状态的函数
    state: ():CartState => {
        return {
            name: '小梦', 
            age: 18,
            goodsList: [{id: 1, name: '手机', num: 1}] //  as Goods[]
        }
    },

    // getters用于定义派生属性,相当于vue组件中的computed
    getters: {
        // 接收state作为第一个参数,使用箭头函数声明,方法体中不能使用到this,结果是 undefined
         userType: (state) => { 
            // console.log('getters>userType:state', state.name
            console.log('userType:this', this) // undefined
            // 当 age 值小于 18,则  userType  值为 未成年人; 大于等于18 , 则  userType  值为成年人 
            return state.age < 18 ? '未成年人': '成年人';
        },

        // 使用普通函数方式声明Getter: 方法体可以使用 this 访问整个store实例
        getGoodsById (state) {
            console.log('getters>getGoodsById>state', state.name, this.name);
            // 通过返回一个函数,该函数可以接收调用方传递的任意参数
            // return (id: number) => {
            //     // 从购物车中查找相关的商品
            //     return this.goodsList.find(item => item.id == id);
            // }
            return (id:number) => this.goodsList.find(item => item.id == id);
        }
    },

    // 定义业务逻辑,类似于methods
    actions: {
        // 添加商品到购物车
        addGoods (goods: Goods) {
            // goods.num/1转成数值
            if (goods.num) goods.num = goods.num/1;
            // console.log('goods', goods)
            // 查询购物车中是否存在此商品,存在则数量累加,不存在则追加商品到数组中
           const target = this.goodsList.find(item => item.id == goods.id);
           if (target)  target.num += goods.num; 
           else this.goodsList.push(goods);
        },
    }
})
import { defineStore } from "pinia";
import { ref, computed } from 'vue';
/**
 * defineStore 方法的参数2:采用类 setup 函数形式
 *  ref 就是 state 属性
 *  computed 就是 getters
 *  function 就是  actions
 */
export const useCounterStore = defineStore('counter', () => {
    // 定义 store 中 state 属性
    const count = ref(0);
    const name = ref('张三');

    // 定义 store 中 gettters
    const doubleCount = computed(() => count.value * 2);

    // 定义 store 中 actions 
    function addCount() {
        count.value++;
    }

    return { 
        count, // count: count
        doubleCount,
        addCount
    }

})
3、view目录
<script setup lang="ts">
import { useCartStore } from '@/stores/cart';
import { useCounterStore } from '@/stores/counter';

import { storeToRefs } from 'pinia';
import { ref } from 'vue';

const cartStore = useCartStore();
const counterStore = useCounterStore();
console.log('counterStore', counterStore.count, counterStore.doubleCount);

// 通过store实例获取状态
console.log('获取state状态', cartStore.name, cartStore.$state)

// 直接从store中解构出来的state状态,不是响应式的
// let { age } = cartStore;

// 使用 storeToRefs 将状态转成一个个ref,再进行解构出来的状态是响应式的
let { age } = storeToRefs(cartStore); // age: ref类型的state
// console.log('age', age.value)

function handleAddAge() {
  // age++; // 从store中直接解构出来的state状态,不是响应式的
  // age.value++; // storeToRefs转成ref后是响应式的
  // cartStore.age++; // 通过store实例操作状态,是响应式的
  // cartStore.name = '小明';

  // 通过 $patch 方法接收一个对象,可以同时修改多个状态属性
  // cartStore.$patch({
  //   name: '小花',
  //   age: cartStore.age+1
  // });

  // $patch 接收一个带state参数的回调函数,
  cartStore.$patch((state) => {
    // console.log('$patch>state', state)
    state.name = '小飞';
    state.age = 35;
    state.goodsList.push({id: state.goodsList.length+1, name: '电脑', num: 2})
  })

}

function handleResetState() {
  // 将 state 状态重置为初始值
  cartStore.$reset();
}

/**
 * 监听state状态变化:使用store的 $subscribe() 方法来监听
 * 参数state是更新后的状态对象
 * 
 * 一般用于状态变化后,将状态持久化保存
 */
cartStore.$subscribe((mutation, state) => {
  console.log('监听state', mutation.storeId, state.name)
  
  // 当状态发生变化后,将整个state持久化存储
  localStorage.setItem('cart', JSON.stringify(state));

  // const cartState = JSON.parse(localStorage.getItem('cart')) ;
  // console.log('cartState', cartState?.age)
})

console.log('获取Getter派生属性值', cartStore.userType);


// 加入购物车逻辑
const goodsId = ref<number>(1);
const goodsName = ref<string>('');
const goodsNum = ref<number>(1);

function handleAddCart() {
  const goods = {id: goodsId.value, name: goodsName.value, num: goodsNum.value};
  // 触发store中的action
  cartStore.addGoods(goods);
}

</script>

<template>
  <div>
    <p>用户名:{{ cartStore.name }}</p>
    <p>年龄:{{ age }}</p>
    <p>购物车:</p>
    <ul>
      <li v-for="(item, index) in cartStore.goodsList" :key="index">
        商品Id: {{ item.id }},名称:{{ item.name }},数量:{{ item.num }}
      </li>
    </ul>
    <input v-model="goodsId" placeholder="商品Id"> &nbsp;
    <input v-model="goodsName" placeholder="商品名称"> &nbsp;
    <input v-model="goodsNum" placeholder="商品数量"> &nbsp;
    <button @click="handleAddCart">加入购物车</button>
    <br>
    <br>
    <button @click="handleAddAge">年龄+1</button>

    <br><br>
    <button @click="handleResetState">重置状态State</button>

    <p>获取 Getter 派生属性:{{ cartStore.userType }}</p>
    <p>向 Getter 派生属性传递参数:{{ cartStore.getGoodsById(1) }}</p>

    <h3>Setup Store</h3>
    <p>获取state:{{ counterStore.count }} , {{ counterStore.doubleCount }}</p>
    <button @click="counterStore.addCount">count+1</button>
  </div>
</template>

<style scoped>

</style>
posted on 2023-11-20 19:30  一路繁花似锦绣前程  阅读(36)  评论(0)    收藏  举报