Vue3使用指南笔记

1、Vue基础

1.1 初始化vue项目

Vue

  1. 前端框架
  2. 渐进式
  3. 声明式
  4. 组件化

前置知识

  1. HTML&CSS
  2. JavaScript基础知识(ES6后的知识)
  3. Node.js
  4. Vite/Webpack(可选)
  5. git(可选)
  6. TypeScript(可选)

环境

node.js 16+

代码

  1. 创建项目 npm create vue
  2. 根据提示填写信息
  3. 进入项目目录,输入npm install安装依赖
  4. 使用npm run dev来启动开发模式
  5. 访问项目路径

手写

main.js主文件、入口文件

import { createApp } from "vue"
import App from "@/App.vue"

// 创建应用实例
/* 
  createApp()
    - 用来创建一个应用实例
    - 参数:
        需要一个根组件作为参数
*/
const app = createApp(App)

// 将应用实例挂载到页面中
app.mount("#app")

App.vue根组件

<template>
  <h1>你好,Vue</h1>
</template>

组件

组件是构成一个项目的基本单位,一个项目就是有多个组件混合而成的。编写项目实际上就是在定义组件!

在Vue中我们使用的是单文件组件,组件中的所有代码(包括结构、表现、行为)统一写在一个.vue文件中。一个Vue文件可以包含三个部分:

  1. 结构:<template></template>
  2. 表现:<style></style>
  3. 行为:<script></script>

API风格

  1. 选项式API<script> export default { // data() 返回的属性将会成为响应式的状态 // 并且暴露在 `this` 上 data() { return { count: 0 } }, // methods 是一些用来更改状态与触发更新的函数 // 它们可以在模板中作为事件处理器绑定 methods: { increment() { this.count++ } }, // 生命周期钩子会在组件生命周期的各个不同阶段被调用 // 例如这个函数就会在组件挂载完成后被调用 mounted() { console.log(`The initial count is ${this.count}.`) } } </script> <template> <button @click="increment">Count is: {{ count }}</button> </template>
  2. 组合式API<script setup> import { ref, onMounted } from 'vue' // 响应式状态 const count = ref(0) // 用来修改状态、触发更新的函数 function increment() { count.value++ } // 生命周期钩子 onMounted(() => { console.log(`The initial count is ${count.value}.`) }) </script> <template> <button @click="increment">Count is: {{ count }}</button> </template>

组件的本质

组件本质上就是一个JS的模块!组件中的所有代码最终都会转换为JS。之所以将template和style都转换为js代码就是为了方便我们动态的去显示页面,简单说就是方便向其中添加变量。

在template中,我们是以html的形式在编写js的代码。在style中,我们是以css的形式编写js的代码!

单文件组件(.vue)—-编译—-> JS代码,由编译器负责!

虚拟DOM

组件并没有被直接转换DOM代码,因为DOM操作是比较耗费性能的,非常容易触发浏览器的重绘重排。如果直接将组件转换为DOM代码,大概率不会有太好的性能。

为了解决这个问题,可以将所有的需要操作DOM的代码给它汇总起来,然后进行统一处理,这样就可以避免重复的DOM操作,提高浏览器的渲染性能。

用普通的JS对象来描述真实的DOM结构 —— 虚拟DOM

虚拟DOM —渲染器—> 真实DOM

单文件组件(.vue)—-编译器—-> 虚拟DOM —渲染器—> 真实DOM

1.2 插值语法

1.2.1 插值语法简介

//App.vue
<template>
  <h1>Hello Vue</h1>
  <!-- 
    插值语法
      - 在vue中使用{{  }}来向模板中插值
      - 规则:
        1. 插值时可以使用任意的表达式
              表达式:有返回值的语句
        2. 插值的作用域并不是全局作用域
              只能访问部分的全局变量
              可以手动指定:
                app.config.globalProperties.hello = "哈哈哈"
        3. 它会自动对html进行转义(避免xss攻击)

      v-html指令(慎用)
        - 对内容进行原样输出
        - 使用时要确保内容不是用户传递的
   -->
  <h2>{{ msg }}</h2>
  <div v-html="text"></div>
</template>

<script setup>
const msg = "今天天气真不错!"
const text = "<h1>这是一段文字</h1>"
</script>
//main.js
import { createApp } from "vue"
import App from "@/App.vue"

// 创建应用实例
/* 
  createApp()
    - 用来创建一个应用实例
    - 参数:
        需要一个根组件作为参数
*/
const app = createApp(App)

app.config.globalProperties.hello = "哈哈哈"

// 将应用实例挂载到页面中
app.mount("#app")

1.2.2 属性插值

<template>
  <h1 :="obj">Hello Vue,{{ null }}</h1>
  <!-- 
    属性插值
      - 使用v-bind指令来进行属性插值
      - 特点:
        1. v-bind可以简写为:
        2. v-bind可以直接传递对象
        3. 设置布尔值时,如果为true则添加属性,为false则移除属性
   -->
  <input type="text" :disabled="true" />
</template>

<script setup>
const text = "今天天气真不错!"
const obj = {
  title: "hello",
  class: "header",
  id: "box",
}
</script>

1.3 响应式对象

1.3.1 响应式对象简介

<template>
  <h1>Hello Vue</h1>
  <ul>
    <li>{{ stu.name }}</li>
    <li>{{ stu.age }}</li>
    <li>{{ stu.gender }}</li>
  </ul>

  <!-- 
    v-on指令
      - 用来为元素绑定事件
      - 可以使用@简写

   -->
  <button @:click="stu.age++">修改年龄</button>
</template>

<script setup>
import { reactive } from "vue"

/* 
  我们这里创建的变量是一个普通的值,
    它不会在值发生变化时触发页面的刷新
    换句话这个值只在初始化时和结构绑定一次,之后便在无关系

  响应式对象
    - 响应式对象会和界面发生绑定,当对象发生变化时也会触发界面的刷新
        从而使其可以显示最新的结果
    - reactive()
      - 用来创建响应式对象
*/

const stu = reactive({
  name: "孙悟空",
  age: 18,
  gender: "男",
})

console.log(stu)
</script>

1.3.2 响应式原理

<template>
  <h1>Hello Vue</h1>
  <ul>
    <li>{{ stu.name }}</li>
    <li>{{ stu.age }}</li>
    <li>{{ stu.gender }}</li>
  </ul>

  <!-- 
    v-on指令
      - 用来为元素绑定事件
      - 可以使用@简写

   -->
  <button @:click="stu.age++">修改年龄 - stu</button>
  <hr />
  <button @:click="obj.age++">修改年龄 - obj</button>
</template>

<script setup>
import { reactive } from "vue"

/* 
  我们这里创建的变量是一个普通的值,
    它不会在值发生变化时触发页面的刷新
    换句话这个值只在初始化时和结构绑定一次,之后便在无关系

  响应式对象
    - 响应式对象会和界面发生绑定,当对象发生变化时也会触发界面的刷新
        从而使其可以显示最新的结果
    - reactive()
      - 用来创建响应式对象
*/

const obj = {
  name: "孙悟空",
  age: 18,
  gender: "男",
}

const stu = reactive(obj)
</script>
//demo.js
const obj = {
  name: "孙悟空",
  age: 18,
  gender: "男",
}

const objProxy = new Proxy(obj, {
  get(target, propName) {
    // 做一些其他操作,记录谁使用了该值
    console.log("xxxx访问了" + propName)
    return target[propName]
  },

  set(target, propName, value) {
    console.log(propName + "发生变化了,所有使用该属性的组件重新渲染")
    target[propName] = value
  },
})

// 对代理对象所做的操作,最终都会在原对象上体现出来
obj.age = 29
console.log(obj.age)

1.3.3 浅层响应式对象

<template>
  <h1>Hello Vue</h1>
  <ul>
    <li>{{ stu.name }}</li>
    <li>{{ stu.age }}</li>
    <li>{{ stu.gender }}</li>
    <ul>
      <li>{{ stu.son.name }}</li>
      <li>{{ stu.son.age }}</li>
    </ul>
  </ul>
  <button @:click="stu.son.age++">修改年龄 - stu</button>
</template>

<script setup>
import { reactive, shallowReactive } from "vue"
/* 
  reactive()创建的对象是一个深层响应式对象
  shallowReactive() 用来创建一个浅层响应式对象
*/
const stu = shallowReactive({
  name: "孙悟空",
  age: 18,
  gender: "男",
  son: {
    name: "孙悟饭",
    age: 2,
  },
})
</script>

1.3.4 ref

<template>
  <h1>Hello Vue</h1>
  <ul>
    <li>{{ stu.name }}</li>
    <li>{{ stu.age }}</li>
    <li>{{ stu.gender }}</li>
  </ul>
  <button @:click="stu = { name: '猪八戒', age: 28, gender: '男' }">
    修改年龄 - stu
  </button>

  <hr />

  <h2>{{ count }}</h2>
  <button @:click="count++">增加</button>

  <hr />
  {{ obj.prop.value }}
  <button @:click="obj.prop.value++">增加</button>

  <hr />

  <ul>
    <li>{{ name }}</li>
    <li>{{ age }}</li>
    <li>{{ gender }}</li>
  </ul>
  <button @:click="age++">修改年龄 - stu</button>
</template>

<script setup>
import { reactive, ref, toRefs, toRef, shallowRef } from "vue"
/* 
  reactive的问题
    1. reactive创建的响应式数据,只能对对象生效,无法应用于原始值
    2. reactive创建的响应式数据无法直接覆盖
    3. reactive创建的响应数据不能解构,解构完的数据就变成普通数据

  为了解决reactive这些问题,vue中还为我们提供了另外一种创建响应式数据的方式
    ref()
    - ref的本质其实就是为数据外部多套了一层:
      ref(0) --> {value:0}

      ref({
        name: "孙悟空",
        age: 18,
        gender: "男",
      })  --> {value:{xxx}}

    - 为了方便我们使用,在模板中使用ref值时,值会自动解包
        并且注意只有顶层数据才会自动解
    
    toRefs()
      - 可以将响应式数据解构为ref,使其保持响应性
    toRef()
      - 将指定数据从响应式对象中取出,保持其响应性
    
    shallowRef()
      - 创建一个浅层的ref值
*/

const count = ref(0)

const stu = ref({
  name: "孙悟空",
  age: 18,
  gender: "男",
})

const stu2 = reactive({
  name: "孙悟空",
  age: 18,
  gender: "男",
})

const { name, age, gender } = toRefs(stu2)

const age2 = toRef(stu2, "age")


// 这种情况不能自动解包
const obj = {
  prop: ref(10),
}
</script>

1.4 计算属性

1.4.1 计算属性简介

<template>
  <h1>Hello Vue</h1>
  <ul>
    <li>{{ stu.name }}</li>
    <li>{{ stu.age }}</li>
    <li>{{ stu.gender }}</li>
    <li>状态:{{ status }}</li>
  </ul>
  <button @:click="stu.age++">修改年龄</button>

  <button @:click="stu.name = '猪八戒'">修改姓名</button>
</template>

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

const stu = ref({
  name: "孙悟空",
  age: 15,
  gender: "男",
})

/* 
  computed() 用来设置计算属性,用来表示一些比较复杂的响应式数据值
    计算属性只有在依赖项发生变化时才会重新计算,其他属性变化会使用缓存值
*/
const status = computed(() => {
  console.log("计算属性执行了~~~~")
  if (stu.value.age >= 60) {
    return "老年"
  } else if (stu.value.age >= 30) {
    return "中年"
  } else if (stu.value.age >= 18) {
    return "成年"
  } else {
    return "未成年"
  }
})

// 函数是不考虑依赖项的,只要响应式对象发生变化,函数就会执行
function test() {
  console.log("test()执行了~~~~")
  if (stu.value.age >= 60) {
    return "老年"
  } else if (stu.value.age >= 30) {
    return "中年"
  } else if (stu.value.age >= 18) {
    return "成年"
  } else {
    return "未成年"
  }
}
</script>

1.4.2 可以修改的计算属性

<template>
  <h1>Hello Vue</h1>
  <ul>
    <li>{{ stu.name }}</li>
    <li>{{ stu.age }}</li>
    <li>{{ stu.gender }}</li>
    <li>状态:{{ status }}</li>
  </ul>
  <button @:click="stu.age++">修改年龄</button>

  <button @:click="stu.name = '猪八戒'">修改姓名</button>
  <button @:click="status = '老年'">修改状态</button>
</template>

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

const stu = ref({
  name: "孙悟空",
  age: 15,
  gender: "男",
})

/* 
  也可以将计算属性设置为可以修改的属性
  注意:
    1. 不要在getter中执行有副作用的代码
    2. 尽量不要创建可一个修改的计算属性
*/
const status = computed({
  get() {
    if (stu.value.age >= 60) {
      return "老年"
    } else if (stu.value.age >= 30) {
      return "中年"
    } else if (stu.value.age >= 18) {
      return "成年"
    } else {
      return "未成年"
    }
  },

  set(value) {
    switch (value) {
      case "老年":
        stu.value.age = 60
        break
      case "中年":
        stu.value.age = 30
    }
  },
})
</script>

1.5 条件渲染

1.5.1 v-if

<template>
  <h1>条件渲染</h1>
  <h2>年龄:{{ age }} - <button @click="age++">增加</button></h2>

  <!-- 
    v-if 指令
      - 根据条件来决定是否显示元素
    v-else-if
    v-else
      - 可以写在v-if后,或者v-else-if后

    处理方式:
      条件满足时,添加元素,不满足时,删除元素

    判断时,可以使用template作为外部的容器,它不会在页面中添加多余的标签
   -->
  <h3 v-if="age >= 60">老年人!</h3>
  <h3 v-else-if="age >= 30">中年人!</h3>
  <h3 v-else-if="age >= 18">成年人!</h3>
  <h3 v-else>未成年</h3>

  <hr />

  <template v-if="age >= 18">
    <p>锄禾日当午</p>
    <p>汗滴禾下土</p>
  </template>
  <template v-else>
    <p>此地别燕丹</p>
    <p>壮士发冲冠</p>
  </template>
</template>

<script setup>
import { ref } from "vue"
const age = ref(17)
</script>

1.5.2 v-show

<template>
  <h1>条件渲染</h1>
  <h2>年龄:{{ age }} - <button @click="age++">增加</button></h2>

  <!-- 
    v-show
      - 也可以用来切换元素的显示
      - 使用v-show切换元素时,只是切换的元素的显示状态
          切换的是元素disable样式

    对比:
      v-if,条件满足元素就存在,不满足就不存在(结构上)
        初始化性能比较好,切换时性能差一些
        切换频率低的用v-if

      v-show,条件满足就显示,不满足就隐藏(样式表现上)
        初始化性能差一些,切换性能较好
        切换频率高的用v-show
   -->

  <div v-show="age >= 18">
    <p>锄禾日当午</p>
    <p>汗滴禾下土</p>
  </div>
  <div v-show="age < 18">
    <p>此地别燕丹</p>
    <p>壮士发冲冠</p>
  </div>
</template>

<script setup>
import { ref } from "vue"
const age = ref(17)
</script>

1.5.3 v-for

<template>
  <h1>列表渲染</h1>
  <ul>
    <!-- 
      v-for
        - 用于在模板中遍历列表
     -->
    <li v-for="(stu, index) in students">
      {{ index }} - {{ stu.name }} - {{ stu.age }} - {{ stu.gender }}
    </li>
  </ul>

  <ul>
    <li v-for="({ name, age, gender }, index) in students">
      {{ index }} - {{ name }} - {{ age }} - {{ gender }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from "vue"

const students = ref([
  {
    name: "孙悟空",
    age: 18,
    gender: "男",
  },
  {
    name: "猪八戒",
    age: 28,
    gender: "男",
  },
  {
    name: "沙和尚",
    age: 38,
    gender: "男",
  },
])
</script>

1.5.4 遍历对象

<template>
  <h1>列表渲染</h1>

  <ul>
    <li v-for="(value, name, index) in stu">
      {{ value }} - {{ name }} - {{ index }}
    </li>
  </ul>

  <!-- <template v-for="(value, name, index) in stu">
    {{ value }} - {{ name }} - {{ index }}
  </template> -->

  <ul>
    <!-- 
      v-for v-if尽量不要在同一标签中使用
     -->
    <template v-for="item in students">
      <li v-if="item.age >= 18">
        {{ item.name }} -- {{ item.age }} -- {{ item.gender }}
      </li>
    </template>
  </ul>
</template>

<script setup>
import { ref } from "vue"

const stu = ref({
  name: "孙悟空",
  age: 18,
  gender: "男",
})

const students = ref([
  {
    name: "孙悟空",
    age: 18,
    gender: "男",
  },
  {
    name: "猪八戒",
    age: 28,
    gender: "男",
  },
  {
    name: "沙和尚",
    age: 38,
    gender: "男",
  },
  {
    name: "唐僧",
    age: 16,
    gender: "男",
  },
])
</script>

1.5.5 关于key的问题

<template>
  <h1>列表渲染</h1>
  <button @click="students.unshift({ name: '白骨精', age: 16, gender: '女' })">
    添加学生
  </button>

  <ul>
    <li v-for="(stu, index) in students" :key="stu.name">
      {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}
      <input type="text" />
    </li>
  </ul>
</template>

<!-- 
  vue的渲染机制
    - 当我们响应式数据(state)发生变化后,会触发界面的重新渲染
    - 每次重新渲染时并不是渲染整个页面,
        而是只对发生变化的的部分进行重新渲染
    - 每次重新渲染时,vue都会将新的虚拟DOM和旧的虚拟DOM进行比较,
        只会对发生变化的元素进行重新渲染(diff)
    - 我们在使用diff算法比较一个列表时是按照顺序进行比较的,
        所以当我们向列表前边插入元素时,会使得列表中的每个元素都会重新渲染
        性能变差
  key
    - 可以通过key属性来解决这个问题,可以为元素设置一个唯一的key
      有了key后,在做diff比较时,便会按照相同的key进行比较,而不是按顺序比较
    - 建议遍历列表时,每次都要为元素指定一个唯一值作为key

    旧:
      li 孙悟空 
      li 猪八戒
      li 沙和尚
      li 唐僧

    新:
      li 白骨精
      li 孙悟空
      li 猪八戒
      li 沙和尚
      li 唐僧

 -->

<script setup>
import { ref } from "vue"

const students = ref([
  {
    name: "孙悟空",
    age: 18,
    gender: "男",
  },
  {
    name: "猪八戒",
    age: 28,
    gender: "男",
  },
  {
    name: "沙和尚",
    age: 38,
    gender: "男",
  },
  {
    name: "唐僧",
    age: 16,
    gender: "男",
  },
])
</script>

1.6 事件处理

1.6.1 事件处理器

<template>
  <h1>事件处理</h1>
  <h2>{{ count }}</h2>
  <button @click="count++">增加</button>
  <button @click="clickHander">增加2</button>
  <button @click="clickHander2(100, $event)">增加3</button>
</template>

<script setup>
import { ref } from "vue"

/* 
  1. 内联事件处理器
      <button @click="count++">增加</button>
      特点:直接在事件写代码,事件触发时代码会执行
      适用:简单逻辑
      事件对象:通过$event来访问

  2. 方法事件处理器
      <button @click="clickHander">增加2</button>
      特点:给属性传递一个回调函数,事件触发时会调用回调函数
      适用:复杂的逻辑
      区分:如果传递的是一个标识符(clickHander),
            或一个访问路径(obj.fn),此时会被判断为方法处理器
      事件对象:
        事件对象作为函数的第一个参数传递

  vue的事件对象就是原生DOM中的事件对象!
*/

function clickHander(event) {
  count.value++
}

function clickHander2(num, event) {
  count.value = num
  alert(event)
}

const count = ref(0)
</script>

1.6.2 事件修饰符

<template>
  <h1>事件修饰符</h1>
  <div @click.self="divHandler" class="box1">
    <button @click.once="buttonHandler">点我一下</button>
  </div>

  <input type="text" @keydown.ctrl.1="console.log(123)" />
</template>

<script setup>
/* 
  事件的修饰符
    - 通过修饰符可以单独为事件设置一些功能
      .stop 停止事件传播
      .prevent 取消默认行为
      .self 只有元素自身才会触发事件
      .capture 捕获阶段触发事件
      .once 一次性事件
      .passive 移动端优化滚动性能
  按键修饰符
    .enter
    .tab
    .delete (捕获“Delete”和“Backspace”两个按键)
    .esc
    .space
    .up
    .down
    .left
    .right
    .ctrl
    .alt
    .shift
    .meta
    .exact
  鼠标按键
    .left
    .right
    .middle

*/
function divHandler() {
  alert("div")
}

function buttonHandler(event) {
  // event.stopPropagation() // 停止事件的传播
  alert("button")
}
</script>
<style>
.box1 {
  width: 200px;
  height: 200px;
  background-color: #bfa;
}
</style>

1.6.3 表单输入

<template>
  <h1>表单输入</h1>
  <input type="text" :value="text" @input="text = $event.target.value" />
  <hr />
  <h2>{{ text }}</h2>
  <button @click="text = 'hello'">点我一下</button>
  <!-- 
    用户输入内容会影响到text变量,
      text的变化也会影响到input,这种我们称为双向绑定

    v-model指令
      - 用来为将表单项和响应式数据进行双向绑定
      - 使用修饰符:
        .lazy 使用change绑定事件
        .number 值自动转换为数字
        .trim 自动去除前后空格

   -->
  <hr />
  <input type="text" v-model="text" />

  <input type="radio" name="gender" value="male" v-model="gender" />
  <input type="radio" name="gender" value="female" v-model="gender" />
  <h3>{{ gender }}</h3>
  <hr />
  <input type="checkbox" name="hobby" value="a" v-model="hobby" />
  <input type="checkbox" name="hobby" value="b" v-model="hobby" />
  <input type="checkbox" name="hobby" value="c" v-model="hobby" />

  <h3>{{ hobby }}</h3>
</template>

<script setup>
import { ref } from "vue"

const text = ref("")
const gender = ref("")
const hobby = ref(["a", "c"])
</script>

1.6.4 练习-电影搜索工具-结构

<template>
  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input class="search-inp" type="text" placeholder="请输入电影名称" />
    <ul class="movie-list">
      <li>加勒比海盗1</li>
      <li>加勒比海盗2</li>
      <li>加勒比海盗3</li>
      <li>加勒比海盗4</li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div class="movie-container">
    <div class="cover">
      <img
        src="https://image.tmdb.org/t/p/w500/7pG8JrrYoVujP2GxDbdE9dA6AIq.jpg"
        
      />
    </div>
    <div class="info">
      <h2>加勒比海盗</h2>
      <div>
        <h3>电影的简介</h3>
        <p>
          故事发生在17世纪,传说中海盗最为活跃的加勒比海。风趣迷人的杰克·斯伯洛,是活跃在加勒比海上的海盗,拥有属于自己的“黑珍珠”号海盗船。对他来说,最惬意的生活就是驾驶着“黑珍珠”号在加勒比海上游荡,自由自在的打劫过往船只。但不幸的是,他的仇敌,老谋深算的巴尔巴罗萨船长偷走了他的“黑珍珠
        </p>
      </div>
      <p><span>上映年份:</span> 2013</p>
      <p><span>评分:</span> 10</p>
      <p><span>评分人数:</span> 2013231</p>
    </div>
  </div>
</template>

1.6.5 练习-电影搜索工具-样式

<script setup>
import { ref } from "vue"
const isShowMovieList = ref(false)
</script>

<template>
  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input class="search-inp" type="text" placeholder="请输入电影名称" />
    <ul v-show="isShowMovieList" class="movie-list">
      <li>加勒比海盗1</li>
      <li>加勒比海盗2</li>
      <li>加勒比海盗3</li>
      <li>加勒比海盗4</li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div class="movie-container">
    <div class="cover">
      <img
        src="https://image.tmdb.org/t/p/w500/7pG8JrrYoVujP2GxDbdE9dA6AIq.jpg"
        alt="加勒比海盗"
      />
    </div>
    <div class="info">
      <h2>加勒比海盗</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          故事发生在17世纪,传说中海盗最为活跃的加勒比海。风趣迷人的杰克·斯伯洛,是活跃在加勒比海上的海盗,拥有属于自己的“黑珍珠”号海盗船。对他来说,最惬意的生活就是驾驶着“黑珍珠”号在加勒比海上游荡,自由自在的打劫过往船只。但不幸的是,他的仇敌,老谋深算的巴尔巴罗萨船长偷走了他的“黑珍珠
        </p>
      </div>
      <p><span>年份:</span> 2013</p>
      <p><span>评分:</span> 10</p>
      <p><span>评分人数:</span> 2013231</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 800px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 800px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 800px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 800px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.6.6 练习-电影搜索工具-完成练习

<script setup>
import { ref, watch } from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

// 用户输入内容后,调用api检索数据
// 当keyword发生变化后,要加载数据
watch(keyword, async () => {
  if (keyword.value === "") {
    isShowMovieList.value = false
    return
  }

  // 加载数据
  // http://api.lilichao.com/movies/filter?title=xxx
  const res = await fetch(
    "http://api.lilichao.com/movies/filter?title=" + keyword.value
  )
  const data = await res.json()

  // 显示电影列表
  isShowMovieList.value = true
  // 将数据设置到ref中
  movieList.value = data
}) // 侦听器,用来监听响应式状态

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.6.7 练习-电影搜索工具-侦听器-数据源

<script setup>
import { reactive, ref, watch } from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const obj = reactive({ name: "孙悟空", age: 18, xxx: { num: 0 } })

// 用户输入内容后,调用api检索数据
// 当keyword发生变化后,要加载数据
// watch(keyword, async () => {
//   if (keyword.value === "") {
//     isShowMovieList.value = false
//     return
//   }

//   // 加载数据
//   // http://api.lilichao.com/movies/filter?title=xxx
//   const res = await fetch(
//     "http://api.lilichao.com/movies/filter?title=" + keyword.value
//   )
//   const data = await res.json()

//   // 显示电影列表
//   isShowMovieList.value = true
//   // 将数据设置到ref中
//   movieList.value = data
// }) // 侦听器,用来监听响应式状态

/* 
  watch()
    - 侦听器
    - 用来监听响应式状态,当状态发生变化时其中的回调函数就会执行
    - 参数:
      1.数据源
        - 要监听的响应式状态
        - 可以接受的值:
                ref值、
                响应式对象(reactive)、
                getter函数、
                使用这三种值作为元素的数组
        -  默认情况下,watch创建的侦听器是一个浅层的侦听器,只侦听直接存储到对象中的属性
          {value:{ name: "孙悟空", age: 18 }}
        - 当使用一个响应式对象作为数据源时,它就隐式的创建了一个深层侦听器

      2.回调函数
      3.配置对象
        {
          deep: true, // 创建一个深层的响应式对象
        }
*/
watch([keyword, () => obj.age], () => {
  console.log("watch执行了......")
})

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  {{ obj.name }} -- {{ obj.age }} -- {{ obj.xxx.num }}--
  <button @click="obj.xxx.num++">点我一下 num</button>
  <button @click="obj.age++">点我一下 age</button>
  <button @click="obj.name = '哈哈'">点我一下 name</button>

  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.6.8 练习-电影搜索工具-回调函数

<script setup>
import { reactive, ref, watch } from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const obj = reactive({ name: "孙悟空", age: 18, xxx: { num: 0 } })

// 用户输入内容后,调用api检索数据
// 当keyword发生变化后,要加载数据
watch(keyword, async (newValue, oldValue, onCleanup) => {
  const timer = setTimeout(async () => {
    if (keyword.value === "") {
      isShowMovieList.value = false
      return
    }

    // 加载数据
    // http://api.lilichao.com/movies/filter?title=xxx
    const res = await fetch(
      "http://api.lilichao.com/movies/filter?title=" + keyword.value
    )
    const data = await res.json()

    // 显示电影列表
    isShowMovieList.value = true
    // 将数据设置到ref中
    movieList.value = data
  }, 1000)

  onCleanup(() => {
    clearTimeout(timer)
  })
}) // 侦听器,用来监听响应式状态

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  <!-- {{ obj.name }} -- {{ obj.age }} -- {{ obj.xxx.num }}--
  <button @click="obj.xxx.num++">点我一下 num</button>
  <button @click="obj.age++">点我一下 age</button>
  <button @click="obj.name = '哈哈'">点我一下 name</button> -->

  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.6.9 练习-电影搜索工具-侦听器配置对象

<script setup>
import { reactive, ref, watch } from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const obj = reactive({ name: "孙悟空", age: 18, xxx: { num: 0 } })

// 用户输入内容后,调用api检索数据
// 当keyword发生变化后,要加载数据
// watch(keyword, async (newValue, oldValue, onCleanup) => {
//   const timer = setTimeout(async () => {
//     if (keyword.value === "") {
//       isShowMovieList.value = false
//       return
//     }

//     // 加载数据
//     // http://api.lilichao.com/movies/filter?title=xxx
//     const res = await fetch(
//       "http://api.lilichao.com/movies/filter?title=" + keyword.value
//     )
//     const data = await res.json()

//     // 显示电影列表
//     isShowMovieList.value = true
//     // 将数据设置到ref中
//     movieList.value = data
//   }, 1000)

//   onCleanup(() => {
//     clearTimeout(timer)
//   })
// }) // 侦听器,用来监听响应式状态

/*
  watch()
    - 侦听器
    - 用来监听响应式状态,当状态发生变化时其中的回调函数就会执行
    - 参数:
      1.数据源
        - 要监听的响应式状态
        - 可以接受的值:
                ref值、
                响应式对象(reactive)、
                getter函数、
                使用这三种值作为元素的数组
        -  默认情况下,watch创建的侦听器是一个浅层的侦听器,只侦听直接存储到对象中的属性
          {value:{ name: "孙悟空", age: 18 }}
        - 当使用一个响应式对象作为数据源时,它就隐式的创建了一个深层侦听器

      2.回调函数
        - 当数据源发生变化时,回调函数就会执行
        - 参数:
            1.新值(数组)
            2.旧值(数组)
            3.清理函数onCleanup
              - 可以传递一个回调函数给它,这个回调函数将会在下次watch触发时
                  调用,用来做一些清除上次watch的影响
      3.配置对象
        {
          deep: true, // 创建一个深层的响应式对象
          immediate: true, // 组件初始化后就执行watch
          flush:"pre" // 指定回调函数的触发时机
            pre 表示在做准备工作前,触发回调函数
            post 表示在渲染后触发回调函数
            sync 表示响应式状态发生变化后立即触发,比pre还要早
        }
*/
// watch(keyword, (newValue, oldValue, onCleanup) => {
//   console.log("watch执行了...", newValue)

//   onCleanup(() => {
//     console.log("清理函数~~~~")
//   })
// })
/* 
  watch默认是懒执行的,只有响应式状态发生变化时回调才会执行

  响应式状态变化 --> 准备工作 --> 创建虚拟DOM,渲染真实DOM
*/
watch(
  keyword,
  () => {
    // 通过原生DOM,来查看p元素中的内容
    const p1 = document.getElementById("p1")
    alert(p1.innerHTML)
    // console.log("watch执行了!")
  },
  {
    flush: "sync",
  }
)

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  <p id="p1">{{ keyword }}</p>
  <!-- {{ obj.name }} -- {{ obj.age }} -- {{ obj.xxx.num }}--
  <button @click="obj.xxx.num++">点我一下 num</button>
  <button @click="obj.age++">点我一下 age</button>
  <button @click="obj.name = '哈哈'">点我一下 name</button> -->

  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.6.9.1 watcheffect

<script setup>
import {
  reactive,
  ref,
  watch,
  watchEffect,
  watchPostEffect,
  watchSyncEffect,
} from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const obj = reactive({ name: "孙悟空", age: 18, xxx: { num: 0 } })
/* 
  副作用就是指是否修改响应式状态
    - 没有副作用使用计算属性,有副作用使用侦听器
    - watchEffect()也是用来创建侦听器的,可以自动判断数据源
        只有在回调函数中被使用到的响应式状态才会成为数据源(只修改的状态不是)
      - 参数:
        1. 回调函数
          - 回调函数中不再有新值和旧值
          - 回调函数中只有一个参数 onCleanup
          - 回调函数会在组件初始化后立即执行(为了设置数据源)
        2. 配置对象
          {
            flush:"pre"
          }

  对比:
    watch:
      1. 懒执行
      2. 明确指定数据源(响应式状态、依赖)
      3. 可以访问新旧值     
      
    watchEffect
      1. 立即执行
      2. 自动设置依赖
      3. 不能访问新旧值
*/
watchEffect(() => {
  console.log("watchEffect执行了!!!", keyword.value)
})

// watchPostEffect
// watchSyncEffect

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  <p id="p1">{{ keyword }}</p>
  <!-- {{ obj.name }} -- {{ obj.age }} -- {{ obj.xxx.num }}--
  <button @click="obj.xxx.num++">点我一下 num</button>
  <button @click="obj.age++">点我一下 age</button>
  <button @click="obj.name = '哈哈'">点我一下 name</button> -->

  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.6.9.2 取消侦听器

<script setup>
import {
  reactive,
  ref,
  watch,
  watchEffect,
  watchPostEffect,
  watchSyncEffect,
} from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const obj = reactive({ name: "孙悟空", age: 18, xxx: { num: 0 } })
/* 
  副作用就是指是否修改响应式状态
    - 没有副作用使用计算属性,有副作用使用侦听器
    - watchEffect()也是用来创建侦听器的,可以自动判断数据源
        只有在回调函数中被使用到的响应式状态才会成为数据源(只修改的状态不是)
      - 参数:
        1. 回调函数
          - 回调函数中不再有新值和旧值
          - 回调函数中只有一个参数 onCleanup
          - 回调函数会在组件初始化后立即执行(为了设置数据源)
        2. 配置对象
          {
            flush:"pre"
          }

  对比:
    watch:
      1. 懒执行
      2. 明确指定数据源(响应式状态、依赖)
      3. 可以访问新旧值     
      
    watchEffect
      1. 立即执行
      2. 自动设置依赖
      3. 不能访问新旧值
*/

// 每一个侦听器设置后都会将一个函数作为返回值,通过该函数可以停止侦听器
const unwatch = watchEffect(() => {
  console.log("watchEffect执行了!!!", keyword.value)
})

// console.log(unwatch)

// watchPostEffect
// watchSyncEffect

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  <button @click="unwatch()">取消侦听器</button>
  <p id="p1">{{ keyword }}</p>
  <!-- {{ obj.name }} -- {{ obj.age }} -- {{ obj.xxx.num }}--
  <button @click="obj.xxx.num++">点我一下 num</button>
  <button @click="obj.age++">点我一下 age</button>
  <button @click="obj.name = '哈哈'">点我一下 name</button> -->

  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.7 生命周期

<script setup>
import {
  reactive,
  ref,
  watch,
  watchEffect,
  watchPostEffect,
  watchSyncEffect,
} from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const obj = reactive({ name: "孙悟空", age: 18, xxx: { num: 0 } })
/* 
  副作用就是指是否修改响应式状态
    - 没有副作用使用计算属性,有副作用使用侦听器
    - watchEffect()也是用来创建侦听器的,可以自动判断数据源
        只有在回调函数中被使用到的响应式状态才会成为数据源(只修改的状态不是)
      - 参数:
        1. 回调函数
          - 回调函数中不再有新值和旧值
          - 回调函数中只有一个参数 onCleanup
          - 回调函数会在组件初始化后立即执行(为了设置数据源)
        2. 配置对象
          {
            flush:"pre"
          }

  对比:
    watch:
      1. 懒执行
      2. 明确指定数据源(响应式状态、依赖)
      3. 可以访问新旧值     
      
    watchEffect
      1. 立即执行
      2. 自动设置依赖
      3. 不能访问新旧值
*/

// 每一个侦听器设置后都会将一个函数作为返回值,通过该函数可以停止侦听器
const unwatch = watchEffect(() => {
  console.log("watchEffect执行了!!!", keyword.value)
})

// console.log(unwatch)

// watchPostEffect
// watchSyncEffect

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}
</script>

<template>
  <button @click="unwatch()">取消侦听器</button>
  <p id="p1">{{ keyword }}</p>
  <!-- {{ obj.name }} -- {{ obj.age }} -- {{ obj.xxx.num }}--
  <button @click="obj.xxx.num++">点我一下 num</button>
  <button @click="obj.age++">点我一下 age</button>
  <button @click="obj.name = '哈哈'">点我一下 name</button> -->

  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

ref属性

<script setup>
import { ref, watchEffect } from "vue"
const isShowMovieList = ref(false)

// 创建一个ref来存储关键字
const keyword = ref("")
// 存储电影列表
const movieList = ref([])
// 记录当前显示的电影
const currentMovie = ref(null)

const div = ref(null) // 用来存储DOM对象
const items = ref([])

const movieHander = (movie) => {
  currentMovie.value = movie
  isShowMovieList.value = false
}

const clickHandler = () => {
  alert(div.value)
  // div.value.innerHTML = "hello"
}

watchEffect(async () => {
  if (keyword.value === "") return

  const res = await fetch(
    "http://api.lilichao.com/movies/filter?title=" + keyword.value
  )

  const data = await res.json()

  movieList.value = data
})

/* 
  ref属性,vue属性
    - vue会将当前元素DOM对象,赋值给ref对应的ref值
    - 三种方式:
      ① ref="ref值"
      ② :ref="ele => {}"
      ③ ref="ref数组"
    - 操作原生DOM时,是完全跳过Vue,慎用
        如果用尽量只是读取数据
*/
</script>

<template>
  <button @click="clickHandler">点我一下</button>
  <div id="box1" :ref="(ele) =>{div = ele}"></div>
  <!-- 电影的搜索工具 -->
  <!-- 搜索框的容器 -->
  <div class="filter-container">
    <input
      @click="isShowMovieList = true"
      v-model.trim="keyword"
      class="search-inp"
      type="text"
      placeholder="请输入电影名称"
    />
    <ul v-show="isShowMovieList" class="movie-list">
      <p style="text-align: center" v-if="movieList.length === 0">
        --没有匹配到电影--
      </p>
      <li ref="items" key="m.id" @click="movieHander(m)" v-for="m in movieList">
        {{ m.title }}
      </li>
    </ul>
  </div>

  <!-- 电影信息的容器 -->
  <div v-if="currentMovie" class="movie-container">
    <div class="cover">
      <img :src="currentMovie.poster_path" :alt="currentMovie.title" />
    </div>
    <div class="info">
      <h2>{{ currentMovie.title }}</h2>
      <div>
        <!-- <h3>电影简介</h3> -->
        <p>
          {{ currentMovie.overview }}
        </p>
      </div>
      <p>
        <span>年份:</span>
        {{ new Date(currentMovie.release_date).getFullYear() }}
      </p>
      <p><span>评分:</span> {{ currentMovie.vote_average }}</p>
      <p><span>评分人数:</span> {{ currentMovie.vote_count }}</p>
    </div>
  </div>
</template>

<style>
body {
  background-color: #f0f0f0;
}

.filter-container {
  width: 1000px;
  margin: auto;
}
.search-inp {
  box-sizing: border-box;
  width: 1000px;
  background-color: transparent;
  border: none;
  outline: none;
  height: 100px;
  font-size: 50px;
}

.movie-list {
  padding: 20px 0;
  margin: 0;
  list-style: none;
  background-color: #fff;
  position: absolute;
  width: 1000px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  font-size: 30px;
  color: #333;
}

.movie-list li {
  padding: 10px 20px;
  cursor: pointer;
}

.movie-list li:hover {
  background-color: #333;
  color: #fff;
}

.movie-container {
  width: 1000px;
  margin: auto;
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}

.cover img {
  width: 400px;
  vertical-align: middle;
}

.info {
  padding: 0 30px;
  color: #333;
  display: flex;
  flex-flow: column;
}

.info div {
  flex: auto;
}

.info p span {
  font-weight: bold;
  font-size: 20px;
}
</style>

1.8 组件通信

1.8.1 组件

//App.vue
<script setup>
import Box from "./components/Box.vue"
/* 
  组件
    - 组件就是一个vue文件
    - 一般情况根组件(App.vue)直接放在src目录下
        而其他的子组件则放到components目录下(其他子目录)
    - 组件一般都使用大驼峰命名法
    - 注册组件:
        1.全局注册,在任意组件中都可以使用
            app.component("Box", Box)
            - 将我们注册的名字作为标签名使用即可
            - 全局组件类似于全局变量,可以在任意位置使用
            - 慎用
        2.局部注册
          - 在组合式API,直接通过import引入组件即可
          - 只能在引入处使用
          - 常用的方式

*/
</script>
<template>
  <div>
    <h1>Hello Vue</h1>
    <Box></Box>
  </div>
</template>
// Box.vue
<template>
  <div class="box1"></div>
</template>

<style>
.box1 {
  width: 200px;
  height: 200px;
  background-color: tomato;
}
</style>

1.8.2 defineEmits

<script setup>
// App.vue
import { ref } from "vue"
import Box from "./components/Box.vue"

const count = ref(0)

/* 
  组件间的通信
    - 如何在组件间传递数据
    - 父组件如果给子组件传递数据:
        使用 props 属性
        - 单向数据流,父 -> 子
        - 子组件无法修改父组件传递的属性
            但是Vue对它的限制并不完全,我们还是有方法直接在子组件中修改父组件的响应式状态
            但是,现实的开发中不建议这么做!这样的操作过多会使得项目变得难以维护
        - 可以在根组件中提供修改函数,通过函数对响应式状态进行修改
    - 组件的事件
      - 可以通过组件的事件,为组件设置响应函数
      - 可以通过事件来完成子组件修改父组件中的响应式状态
        也可以通过事件来向父组件中传递数据
*/

const increase = () => {
  count.value++
}

const getData = (data) => {
  console.log("收到数据:", data)
}
</script>
<template>
  <div>
    <h1>Hello Vue</h1>
    <Box :count="count" @increase="increase" @send-data="getData"></Box>
  </div>
</template>
<script setup>
//Box.vue
const props = defineProps(["count"])

/* 
  在模板中,可以同$emit()来触发事件
    $emit()
      - 参数:
          1. 事件名的字符串
          2. ...传递给回调函数的参数
*/
const emit = defineEmits(["increase", "sendData"])

const clickHandler = () => {
  emit("increase")
}
</script>
<template>
  <div class="box1" @click="clickHandler">
    <h1>{{ count }}</h1>
    <button @click="$emit('sendData', { name: '孙悟空' })">发送数据</button>
  </div>
</template>

<style>
.box1 {
  width: 200px;
  height: 200px;
  background-color: tomato;
  display: flow-root;
  color: #fff;
  margin-bottom: 10px;
}

h2 {
  margin: 10px;
}
</style>

1.8.3 组件上的v-model

<script setup>
// App.vue
import { ref } from "vue"
import MyInput from "@/components/MyInput.vue"

const msg = ref("今天天气真不错!")
</script>
<template>
  <div>
    <h1>Hello Vue</h1>

    <!-- <input type="text" v-model="msg" /> -->
    <!-- <input type="text" :value="msg" @input="msg = $event.target.value" /> -->
    <!-- 
    直接在自定义的组件上设置v-model的不好用的!
    - 如果希望自定义的组件可以支持v-model则必须要对组件进行一些设置
    - 当我们在组件上使用v-model时,vue实际上会为组件设置一个动态属性和一个事件
    - 属性名:modelValue 事件名:update:modelValue

 -->
    <MyInput v-model="msg"></MyInput>
    <!-- <MyInput
      :modelValue="msg"
      @update:modelValue="(newValue) => (msg = newValue)"
    ></MyInput> -->
    <h2>{{ msg }}</h2>
  </div>
</template>
<script setup>
//MyInput.vue
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
    type="text"
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />

  <!-- <input type="text" v-model="value" /> -->
</template>

<style>
input {
  width: 500px;
  height: 100px;
  font-size: 50px;
  border: none;
  outline: none;
  border-bottom: 2px #000 solid;
  color: tomato;
}
</style>

1.8.4 v-model补充

<script setup>
import { ref } from "vue"
import MyInput from "@/components/MyInput.vue"

const msg = ref("今天天气真不错!")
</script>
<template>
  <div>
    <h1>Hello Vue</h1>

    <!-- <input type="text" v-model="msg" /> -->
    <!-- <input type="text" :value="msg" @input="msg = $event.target.value" /> -->
    <!-- 
    直接在自定义的组件上设置v-model的不好用的!
    - 如果希望自定义的组件可以支持v-model则必须要对组件进行一些设置
    - 当我们在组件上使用v-model时,vue实际上会为组件设置一个动态属性和一个事件
    - 属性名:modelValue 事件名:update:modelValue
    - 如何在组件上自定义v-model的修饰符:
        - 修饰符实际上就是一个属性
        - 需要在组件中定义属性,来读取v-model的修饰符
        - 修饰符默认值:modelModifiers
    - 通过参数指定属性名
        - v-model:属性名
          属性名:属性名
          事件名:update:属性名
          修饰符属性名:属性名Modifiers

 -->
    <MyInput v-model.upper="msg"></MyInput>
    <!-- <MyInput
      :modelValue="msg"
      @update:modelValue="(newValue) => (msg = newValue)"
    ></MyInput> -->
    <h2>{{ msg }}</h2>
  </div>
</template>
<script setup>
import { computed } from "vue"

// 定义属性
const props = defineProps({
  modelValue: String,
  modelModifiers: {
    default: () => ({}),
  },
})
const emit = defineEmits(["update:modelValue"])
// console.log(props.modelModifiers)

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    if (props.modelModifiers.upper) {
      value = value.toUpperCase()
    }
    emit("update:modelValue", value)
  },
})
</script>

<template>
  <input type="text" v-model="value" />
</template>

<style>
input {
  width: 500px;
  height: 100px;
  font-size: 50px;
  border: none;
  outline: none;
  border-bottom: 2px #000 solid;
  color: tomato;
}
</style>

1.8.5 属性的透传

<script setup>
//App.vue
import { ref } from "vue"
import Box from "@/components/Box.vue"
</script>
<template>
  <!-- 
     自定义属性 props
     自定义事件 emits
          其他 attrs (attributes)  

      既没有在props中定义,也没有在emits中定义的属性,称为attrs
        attrs默认由自行处理,vue会优先对这些属性进行透传(继承)
      
      属性的透传:
        - 就是指将attrs自动设置到组件的根元素上
        - 如果根元素也是一个组件,则属性会逐级透传
        - 透传只发生在单根组件上
      如果不希望将属性透传给元素?
        defineOptions({
          inheritAttrs: false,
        })
      如果组件是一个多根组件怎么办?
        - 在模板中有一个叫$attrs,它存储了所有的透传过来的属性
          可以通过它来手动的指定透传的属性
      在script中可以通过钩子函数来获取attrs
        const attrs = useAttrs()

      透传过去的属性,属性名可以直接使用
      透传过去的事件,需要通过on来访问
   -->
  <div>
    <h1>Hello Vue</h1>
    <Box
      @click="console.log(123)"
      class="hello"
      style="background-color: #bfa"
    ></Box>
  </div>
</template>
<script setup>
//Box.vue
import { useAttrs } from "vue"
import MyButton from "./MyButton.vue"

defineOptions({
  inheritAttrs: false,
})

const attrs = useAttrs()

// console.log(attrs.onClick)
</script>
<template>
  <div class="box1">
    <div class="box2"></div>
  </div>

  <!-- <div class="box2" :style="$attrs.style"></div> -->
</template>

<style>
.box1 {
  width: 200px;
  height: 200px;
  background-color: tomato;
  display: flow-root;
  color: #fff;
  margin-bottom: 10px;
}

.box2 {
  width: 100px;
  height: 100px;
  background-color: skyblue;
}

h2 {
  margin: 10px;
}
</style>
<template>
<!-- Button.vue -->
  <button>点我一下</button>
</template>

1.8.6 插槽

<script setup>
// App.vue
import MyButton from "./components/MyButton.vue"
import Card from "./components/Card.vue"
import Box from "./components/Box.vue"
import { ref } from "vue"

const stus = ref(["白骨精", "猪八戒", "沙和尚"])
</script>
<template>
  <!-- 
    如何在子组件中访问到父组件为其设置标签体
      插槽(slot)
        - 通过插槽,子组件可以获取到父组件传递的标签体
        - 父组件设置标签体,称为“插槽内容”
        - 子组件中设置的<slot>,称为“插槽出口”
        - 模板中的标签在哪定义,作用域就在哪
        - 当前组件模板中的表达式只能访问当前组件中的数据
   -->
  <MyButton>点我一下</MyButton>

  <Card>
    <ul>
      <li v-for="item in stus">{{ item }}</li>
    </ul>
  </Card>

  <Card>
    <p>今天天气真不错</p>
  </Card>

  <Card>
    <Box></Box>
  </Card>
</template>
<script setup>
//Card.vue
import { ref } from "vue"

const count = ref(0)
</script>
<template>
  <div class="card">
    <slot></slot>
  </div>
</template>

<style scoped>
.card {
  padding: 10px;
  margin: 10px 0;
  border-radius: 10px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
</style>
<script setup>
//MyButton.vue
const props = defineProps(["btnText"])
</script>

<template>
  <button>
    <slot></slot>
  </button>
</template>

<style scoped>
button {
  width: 200px;
  height: 50px;
  font-size: 30px;
}
</style>

1.8.7 插槽补充

<script setup>
// App.vue
import MyButton from "./components/MyButton.vue"
import Card from "./components/Card.vue"
import Box from "./components/Box.vue"
import { ref } from "vue"
import Layout from "./components/Layout.vue"

const stus = ref(["白骨精", "猪八戒", "沙和尚"])
</script>
<template>
  <!-- <Layout>
    <template v-slot:header> 头部内容 </template>
    <template v-slot:footer> 底部内容 </template>
    <template v-slot:default> 主要内容 </template>
  </Layout> -->

  <Layout>
    <template #header="h"> 头部内容 {{ h.a }}</template>
    <template #footer="f"> 底部内容 {{ f.a }}</template>
    <template #default="d"> 主要内容 {{ d.a }}</template>
  </Layout>
  <!-- 
    如何在子组件中访问到父组件为其设置标签体
      插槽(slot)
        - 通过插槽,子组件可以获取到父组件传递的标签体
        - 父组件设置标签体,称为“插槽内容”
        - 子组件中设置的<slot>,称为“插槽出口”
        - 模板中的标签在哪定义,作用域就在哪
        - 当前组件模板中的表达式只能访问当前组件中的数据
      <slot>
        - slot也可以设置标签体,它的标签体将会成为默认内容
        - 属性:
            name 插槽的名字
          - 可以为一个组件设置多个插槽,设置内容时可以通过template标签来指定内容的出口
            示例:<slot name="xxx"></slot> <template v-slot:xxx></template>
          - 如果不指定name,则slot的默认name为default
          - 直接写在组件标签体中的内容,默认会使用default为出口
          - v-slot可以简写为#
   -->
  <MyButton>点我一下</MyButton>

  <Card #="data">
    <ul>
      <li v-for="item in stus">{{ item }} -- {{ data.hello }}</li>
    </ul>
  </Card>

  <Card>
    <p>今天天气真不错</p>
  </Card>

  <Card>
    <Box></Box>
  </Card>
</template>
<script setup>
//Card.vue
import { ref } from "vue"

const count = ref(1315)
</script>
<template>
  <div class="card">
    <slot :hello="count">哈哈我是默认内容</slot>
  </div>
</template>

<style scoped>
.card {
  padding: 10px;
  margin: 10px 0;
  border-radius: 10px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
</style>
<template>
<!--Layout.vue-->
  <div class="header">
    <slot a="10" name="header"></slot>
  </div>

  <div class="main">
    <slot a="20"></slot>
  </div>

  <div class="footer">
    <slot a="30" name="footer"></slot>
  </div>
</template>

<style scoped>
.header {
  height: 100px;
  background-color: #bfa;
  margin: 10 auto;
}

.main {
  height: 400px;
  background-color: orange;
  margin: 10 auto;
}

.footer {
  height: 100px;
  background-color: tomato;
  margin: 10 auto;
}
</style>

1.8.8 依赖注入

<script setup>
//App.vue
import { provide, ref } from "vue"
import A from "./components/A.vue"
import C from "./components/C.vue"
import { MSG_KEY } from "./keys"

const msg = ref("今天天气真不错!")

provide(MSG_KEY, msg)
</script>

<template>
  <!-- 
    应用实例(app)-> App -> A组件 -> B组件
                        -> C组件
    - 祖先和后代间要如何通信
    - 可以同过之前的手段来处理这个问题,但是方便吗?
    - 并不方便,尤其是在组件过多的场景下
    
    依赖注入
      - 将组件所依赖的值注入到组件中
      - 使用步骤:
        1.在祖先组件中提供依赖
          provide(key, value)
            - key 字符串 和 符号
              - 开发中推荐使用 符号
            - value可以是任意值

        2.在后代组件中注入依赖
          inject(key)
          inject(key, value)

      - 依赖注入的数据是在祖先和后代之间传递的!


    App -> A -> B -> C -> D -> E -> F
  -->
  <h1>App组件</h1>
  <div class="box">
    <A></A>
    <C></C>
  </div>
</template>

<style scoped>
.box {
  border: 2px red solid;
}
</style>
//keys.js
export const MSG_KEY = Symbol()
//main.js
import { createApp } from "vue"
import App from "@/App.vue"
// import Box from "@/components/Box.vue"

// 创建应用实例
/* 
  createApp()
    - 用来创建一个应用实例
    - 参数:
        需要一个根组件作为参数
*/
const app = createApp(App)

// app.config.globalProperties.hello = "哈哈哈"

// app.component("Box", Box)

app.provide("hello", "通过应用实例提供的数据")

// 将应用实例挂载到页面中
app.mount("#app")
<script setup>
// /componetns/A.vue
import { provide } from "vue"
import B from "./B.vue"
import { MSG_KEY } from "../keys"
// const props = defineProps(["msg"])

provide("hello", "A组件中的数据")
provide(MSG_KEY, "哈哈哈")
</script>
<template>
  <h2>A组件</h2>
  <div class="box">
    <B></B>
  </div>
</template>

<style scoped>
.box {
  margin: 10px;
  border: 2px solid orange;
}
</style>
<script setup>
// /componetns/B.vue
import { inject } from "vue"
import { MSG_KEY } from "../keys"

// const msg = inject(MSG_KEY, () => "嘻嘻嘻", true)
const msg = inject(MSG_KEY, "嘻嘻嘻")
const hello = inject("hello")
</script>

<template>
  <h3>B组件 -- {{ msg }} -- {{ hello }}</h3>
</template>

<style scoped></style>
<script setup>
// /componetns/C.vue
import { inject } from "vue"

const hello = inject("hello")
</script>

<template>
  <h2>C组件 -- {{ hello }}</h2>
</template>

<style scoped></style>

1.8.9 使用响应式对象共享状态

<script setup>
// App.vue
import { provide, ref } from "vue"
import A from "./components/A.vue"
import C from "./components/C.vue"
import { MSG_KEY } from "./keys"

const msg = ref("今天天气真不错!")

provide(MSG_KEY, msg)
</script>

<template>
  <!-- 
    应用实例(app)-> App -> A组件 -> B组件
                        -> C组件 -> D组件
   
    依赖注入只适用于祖先后代之间的状态共享,其他关系的组件无法使用

    任意组件间的通信
      - 可以将响应式状态定义到一个JS文件中并导出
      - 这样任何组件都可以访问到这个状态
      - 示例:
        import { reactive } from "vue"

        export const globalState = reactive({
          msg: "这是在store中设置的数据",
        })

      - 这种方式非常灵活便利,适合小型的项目
      - 缺点:
        1.响应式状态过多时不便于维护
        2.不利于统一开发标准
        3.和开发工具整合的不好(不便于调试)
        4.不支持服务器端渲染
  -->
  <h1>App组件</h1>
  <div class="box">
    <A></A>
    <C></C>
  </div>
</template>

<style scoped>
.box {
  border: 2px red solid;
}
</style>
<script setup>
// /componetns/A.vue
import { provide } from "vue"
import B from "./B.vue"
import { MSG_KEY } from "../keys"
import { globalState } from "../store"
// const props = defineProps(["msg"])

provide("hello", "A组件中的数据")
provide(MSG_KEY, "哈哈哈")
</script>
<template>
  <h2>A组件 - {{ globalState.msg }}</h2>
  <div class="box">
    <B></B>
  </div>
  <button @click="globalState.msg = 'A修改了state'">修改globalState</button>
</template>

<style scoped>
.box {
  margin: 10px;
  border: 2px solid orange;
}
</style>
<script setup>
// /componetns/C.vue
import { inject } from "vue"
import { globalState } from "../store"

const hello = inject("hello")
</script>

<template>
  <h2>C组件 -- {{ globalState.msg }}</h2>
</template>

<style scoped></style>
// store.js
import { reactive } from "vue"

export const globalState = reactive({
  msg: "这是在store中设置的数据",
})

2 Pinia

2.1 pinia 简介

//mian.js
import { createApp } from "vue"
import App from "@/App.vue"
import { createPinia } from "pinia"
// import Box from "@/components/Box.vue"

// 创建应用实例
/* 
  createApp()
    - 用来创建一个应用实例
    - 参数:
        需要一个根组件作为参数
*/
const app = createApp(App)

// 创建pinia的实例
const pinia = createPinia()
// 将它注册为应用的插件
app.use(pinia)

// app.config.globalProperties.hello = "哈哈哈"

// app.component("Box", Box)

app.provide("hello", "通过应用实例提供的数据")

// 将应用实例挂载到页面中
app.mount("#app")
<script setup>
// App.vue
import { useStudentStore } from "./store/store"

// 通过调用函数,获取store中的存储的数据
const stu = useStudentStore()
</script>

<template>
  <h1>Hello Vue - {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}</h1>
  <button type="button" @click="stu.age++">点我一下</button>
  <!-- 
    Pinia的使用
      1.安装 npm i pinia
      2.在应用实例中注册pinia
        const pinia = createPinia()
        app.use(pinia)
      3.创建store
        store的名字
          useXxxStore
          student -> useStudentStore
          cart -> useCartStore
        defineStore()
          - 参数:
              id store的唯一表示,不能重复
              {} store配置
          - 返回值:函数
      4.使用store
          const stu = useStudentStore()

   -->
</template>

<style scoped></style>
// /store/store.js
import { defineStore } from "pinia"
import { reactive } from "vue"

// 定义store
/* 
  store的名字
    useXxxStore
    student -> useStudentStore
    cart -> useCartStore
  defineStore()
    - 参数:
        id store的唯一表示,不能重复
        {} store配置
    - 返回值:函数

*/
export const useStudentStore = defineStore("student", {
  state: () => ({
    name: "孙悟空",
    age: 18,
    gender: "男",
  }),
})

2.2 定义store

<script setup>
// App.vue
import { useStudentStore } from "./store/store"

// 通过调用函数,获取store中的存储的数据
const stu = useStudentStore()
</script>

<template>
  <h1>Hello Vue - {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}</h1>
  <h2>{{ stu.doubleAge }} -- {{ stu.titleName }}</h2>
  <button type="button" @click="stu.setAge(111)">点我一下</button>
  <!-- 
    Pinia的使用
      1.安装 npm i pinia
      2.在应用实例中注册pinia
        const pinia = createPinia()
        app.use(pinia)
      3.创建store
        store的名字
          useXxxStore
          student -> useStudentStore
          cart -> useCartStore
        defineStore()
          - 参数:
              id store的唯一表示,不能重复
              {} store配置
          - 返回值:函数
      4.使用store
          const stu = useStudentStore()

   -->
</template>

<style scoped></style>
// /store/store.js
import { defineStore } from "pinia"
import { computed, reactive, ref } from "vue"

// 定义store
/* 
  store的名字
    useXxxStore
    student -> useStudentStore
    cart -> useCartStore
  defineStore()
    - 参数:
        id store的唯一表示,不能重复
        {} store配置
    - 返回值:函数
   
  概念:
    pinia实例 createPinia()
    useXxx函数 defineStore()
    store实例(响应式对象) use函数

  defineStore的配置对象:
    选项式API(推荐)
      - 配置对象
      - 选项:
          state
            - state用来配置store中存储的数据
            - 这些数据可以直接通过store实例访问
            - 需要一个函数作为值,函数需要返回一个对象,
                对象中的属性就会成为store实例中存储的状态

          getters
            - getters就相当于Vue中的计算属性
            - 需要一个对象作为值,对象中通过方法来设置计算属性,
                方法中可以通过this来访问当前的store实例
            - 计算属性也可以直接通过store实例来访问

          actions
            - actions相当于组件中定义的方法,可以在方法中定义一些操作的逻辑
            - actions中的定义的方法,可以通过store实例直接调用
            - 它也需要一个对象做为值,对象中的方法就是最终的函数
                在方法中可以通过this来访问store实例
    组合式API

*/
export const useStudentStore = defineStore("student", {
  state: () => ({
    name: "孙悟空",
    age: 18,
    gender: "男",
  }),
  getters: {
    doubleAge() {
      return this.age * 2
    },
    titleName: (state) => "Mr." + state.name,
  },
  actions: {
    increase() {
      this.age++
    },

    setAge(age) {
      this.age = age
    },
  },
})

// //组合式API
// export const useStudentStore = defineStore("student", () => {
//   // 定义state
//   const name = ref("猪八戒")
//   const age = ref(28)
//   const gender = ref("男")

//   // 计算属性 getters
//   const doubleAge = computed(() => age.value * 2)
//   const titleName = computed(() => "Mr." + name.value)

//   // actions
//   const increase = () => {
//     age.value++
//   }

//   const setAge = (val) => {
//     age.value = val
//   }

//   return { name, age, gender, doubleAge, titleName, increase, setAge }
// })

2.3 store的属性和方法

<script setup>
// App.vue
import Box from "./components/Box.vue"
import { useStudentStore } from "./store/store"

// 通过调用函数,获取store中的存储的数据
const stu = useStudentStore()
</script>

<template>
  <h1>Hello Vue - {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}</h1>
  <h2>{{ stu.doubleAge }} -- {{ stu.titleName }}</h2>
  <button type="button" @click="stu.setAge(111)">点我一下</button>
  <hr />
  <Box></Box>
  <!-- 
    Pinia的使用
      1.安装 npm i pinia
      2.在应用实例中注册pinia
        const pinia = createPinia()
        app.use(pinia)
      3.创建store
        store的名字
          useXxxStore
          student -> useStudentStore
          cart -> useCartStore
        defineStore()
          - 参数:
              id store的唯一表示,不能重复
              {} store配置
          - 返回值:函数
      4.使用store
          const stu = useStudentStore()

   -->
</template>

<style scoped></style>
<script setup>
// Box.vue
import { useStudentStore } from "@/store/store"
import { toRefs } from "vue"
import { storeToRefs } from "pinia"

const stu = useStudentStore()
const { name, age, gender } = storeToRefs(useStudentStore())
/* 
  store实例
    - 通过use函数返回的对象就是store实例
    - 通过store实例可以完成对其中的响应式状态的各种操作
    - store实例就是一个响应式对象
    - 作用:
      1.可以访问其中的state、getters、actions
      2.可以通过storeToRefs()来完成对store实例的解构
      3.可以直接通过它来修改state
      4.也可以通过$patch()来修改state
      5.可以通过$reset()来重置state
      6.$id、$state

*/

console.log(stu.$state)
</script>

<template>
  <h2>Box</h2>
  <h3>
    {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}-- {{ stu.weapons }}
  </h3>
  <h3>{{ name }} -- {{ age }} -- {{ gender }}</h3>
  <button @click="stu.name = '猪八戒'">修改1</button>
  -
  <button @click="stu.$patch({ age: 888 })">修改2</button>
  -
  <button @click="stu.weapons.push('大师剑')">修改3</button>
  -
  <button @click="stu.$patch({ weapons: [...stu.weapons, '大师剑'] })">
    修改4
  </button>
  -
  <button @click="stu.$patch((state) => state.weapons.push('驱魔剑'))">
    修改5
  </button>
  -
  <button @click="stu.$state = { name: '沙和尚' }">修改5</button>
  -
  <button @click="stu.$reset()">重置</button>
</template>

<style scoped></style>
// /store/store.js
import { defineStore } from "pinia"
import { computed, reactive, ref } from "vue"

// 定义store
/* 
  store的名字
    useXxxStore
    student -> useStudentStore
    cart -> useCartStore
  defineStore()
    - 参数:
        id store的唯一表示,不能重复
        {} store配置
    - 返回值:函数
   
  概念:
    pinia实例 createPinia()
    useXxx函数 defineStore()
    store实例(响应式对象) use函数

  defineStore的配置对象:
    选项式API(推荐)
      - 配置对象
      - 选项:
          state
            - state用来配置store中存储的数据
            - 这些数据可以直接通过store实例访问
            - 需要一个函数作为值,函数需要返回一个对象,
                对象中的属性就会成为store实例中存储的状态

          getters
            - getters就相当于Vue中的计算属性
            - 需要一个对象作为值,对象中通过方法来设置计算属性,
                方法中可以通过this来访问当前的store实例
            - 计算属性也可以直接通过store实例来访问

          actions
            - actions相当于组件中定义的方法,可以在方法中定义一些操作的逻辑
            - actions中的定义的方法,可以通过store实例直接调用
            - 它也需要一个对象做为值,对象中的方法就是最终的函数
                在方法中可以通过this来访问store实例
    组合式API

*/
export const useStudentStore = defineStore("student", {
  state: () => ({
    name: "孙悟空",
    age: 18,
    gender: "男",
    weapons: ["金箍棒", "三根毛"],
  }),
  getters: {
    doubleAge() {
      return this.age * 2
    },
    titleName: (state) => "Mr." + state.name,
  },
  actions: {
    increase() {
      this.age++
    },

    setAge(age) {
      this.age = age
    },
  },
})

// //组合式API
// export const useStudentStore = defineStore("student", () => {
//   // 定义state
//   const name = ref("猪八戒")
//   const age = ref(28)
//   const gender = ref("男")

//   // 计算属性 getters
//   const doubleAge = computed(() => age.value * 2)
//   const titleName = computed(() => "Mr." + name.value)

//   // actions
//   const increase = () => {
//     age.value++
//   }

//   const setAge = (val) => {
//     age.value = val
//   }

//   return { name, age, gender, doubleAge, titleName, increase, setAge }
// })

2.4 state的订阅

<script setup>
// App.vue
import { ref } from "vue"
import Box from "./components/Box.vue"
import { useStudentStore } from "./store/store"

// 通过调用函数,获取store中的存储的数据
const stu = useStudentStore()
const show = ref(true)
</script>

<template>
  <h1>Hello Vue - {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}</h1>
  <h2>{{ stu.doubleAge }} -- {{ stu.titleName }}</h2>
  <button type="button" @click="stu.age++">点我一下</button>
  <hr />
  <button @click="show = !show">切换</button>
  <Box v-if="show"></Box>
  <!-- 
    Pinia的使用
      1.安装 npm i pinia
      2.在应用实例中注册pinia
        const pinia = createPinia()
        app.use(pinia)
      3.创建store
        store的名字
          useXxxStore
          student -> useStudentStore
          cart -> useCartStore
        defineStore()
          - 参数:
              id store的唯一表示,不能重复
              {} store配置
          - 返回值:函数
      4.使用store
          const stu = useStudentStore()

   -->
</template>

<style scoped></style>
<script setup>
// Box.vue
import { useStudentStore } from "@/store/store"
import { toRefs } from "vue"
import { storeToRefs } from "pinia"

const stu = useStudentStore()
const { name, age, gender } = storeToRefs(useStudentStore())
/* 
  store实例
    - 通过use函数返回的对象就是store实例
    - 通过store实例可以完成对其中的响应式状态的各种操作
    - store实例就是一个响应式对象
    - 作用:
      1.可以访问其中的state、getters、actions
      2.可以通过storeToRefs()来完成对store实例的解构
      3.可以直接通过它来修改state
      4.也可以通过$patch()来修改state
      5.可以通过$reset()来重置state
      6.$id、$state
      7.订阅state、action
        stu.$subscribe() -> 订阅state,订阅是和组件绑定的

*/
const stop = stu.$subscribe((mutation, state) => {
  /* 
    mutation
      - 修改的详细信息
        - storeId
          - 当前store的id
        - type
          - 修改方式
              'direct' 直接修改
                  xxx.name = "xxx"
                  xxx.age = 111
                  xxx.$state.gender = "xxx"
              'patch object' 通过补丁对象修改
                  xxx.$patch({xxx})
                  - payload 对象参数
              'patch function' 通过补丁函数修改
                  xxx.$patch(state => state.xxx=xxx)
                  xxx.$state = {}
              
        - events
          - 事件对象,存储了本次操作的详细信息
          - 如果type是'direct',则它是一个对象
              如果type是'patch xxx',则它是一个数组

    state
      - 最新的状态
  */
  // console.log(
  //   mutation.events.key,
  //   mutation.events.newValue,
  //   mutation.events.oldValue
  // )

  // console.log(mutation.events)

  console.log("store变了!!")
  // 在state发生变化后,做一些操作
  // localStorage.setItem(...)
})
</script>

<template>
  <h2>Box</h2>
  <h3>
    {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}-- {{ stu.weapons }}
  </h3>
  <h3>{{ name }} -- {{ age }} -- {{ gender }}</h3>
  <button @click="stu.name = '猪八戒'">修改1</button>
  -
  <button @click="stu.$patch({ age: 888 })">修改2</button>
  -
  <button @click="stu.weapons.push('大师剑')">修改3</button>
  -
  <button @click="stu.$patch({ weapons: [...stu.weapons, '大师剑'] })">
    修改4
  </button>
  -
  <button @click="stu.$patch((state) => state.weapons.push('驱魔剑'))">
    修改5
  </button>
  -
  <button @click="stu.$state = { name: '沙和尚' }">修改6</button>
  -
  <button @click="stu.$reset()">重置</button>

  -
  <button @click="stop">取消订阅</button>
</template>

<style scoped></style>

2.5 action的订阅

<script setup>
// App.vue
import { ref } from "vue"
import Box from "./components/Box.vue"
import { useStudentStore } from "./store/store"

// 通过调用函数,获取store中的存储的数据
const stu = useStudentStore()
const show = ref(true)
</script>

<template>
  <h1>Hello Vue - {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}</h1>
  <h2>{{ stu.doubleAge }} -- {{ stu.titleName }}</h2>
  <button type="button" @click="stu.age++">点我一下</button>
  -
  <button type="button" @click="stu.increase()">increase</button>
  -
  <button type="button" @click="stu.setAge(11)">setAge</button>
  <hr />
  <button @click="show = !show">切换</button>
  <Box v-if="show"></Box>
  <!-- 
    Pinia的使用
      1.安装 npm i pinia
      2.在应用实例中注册pinia
        const pinia = createPinia()
        app.use(pinia)
      3.创建store
        store的名字
          useXxxStore
          student -> useStudentStore
          cart -> useCartStore
        defineStore()
          - 参数:
              id store的唯一表示,不能重复
              {} store配置
          - 返回值:函数
      4.使用store
          const stu = useStudentStore()

   -->
</template>

<style scoped></style>
<script setup>
// Box.vue
import { useStudentStore } from "@/store/store"
import { toRefs } from "vue"
import { storeToRefs } from "pinia"

const stu = useStudentStore()
const { name, age, gender } = storeToRefs(useStudentStore())
/* 
  store实例
    - 通过use函数返回的对象就是store实例
    - 通过store实例可以完成对其中的响应式状态的各种操作
    - store实例就是一个响应式对象
    - 作用:
      1.可以访问其中的state、getters、actions
      2.可以通过storeToRefs()来完成对store实例的解构
      3.可以直接通过它来修改state
      4.也可以通过$patch()来修改state
      5.可以通过$reset()来重置state
      6.$id、$state
      7.订阅state、action
        stu.$subscribe() -> 订阅state,订阅是和组件绑定的
        stu.$onAction() -> 订阅action

*/
stu.$onAction(({ name, store, args, after, onError }) => {
  /* 
    name 触发的action
    store store实例
    args action的参数
    after 设置回调函数
    onError action出错时调用的回调函数

  */
  console.log("action执行前调用的", name)

  after((result) => {
    console.log("action执行后调用的", name, result)
  })

  // 回调函数,会在action前执行
  // console.log("store中的方法调用了~~~~")
})
</script>

<template>
  <h2>Box</h2>
  <h3>
    {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}-- {{ stu.weapons }}
  </h3>
  <h3>{{ name }} -- {{ age }} -- {{ gender }}</h3>
  <button @click="stu.name = '猪八戒'">修改1</button>
  -
  <button @click="stu.$patch({ age: 888 })">修改2</button>
  -
  <button @click="stu.weapons.push('大师剑')">修改3</button>
  -
  <button @click="stu.$patch({ weapons: [...stu.weapons, '大师剑'] })">
    修改4
  </button>
  -
  <button @click="stu.$patch((state) => state.weapons.push('驱魔剑'))">
    修改5
  </button>
  -
  <button @click="stu.$state = { name: '沙和尚' }">修改6</button>
  -
  <button @click="stu.$reset()">重置</button>

  -
  <button @click="stop">取消订阅</button>
</template>

<style scoped></style>
// /store/store.js
import { defineStore } from "pinia"
import { computed, reactive, ref } from "vue"

// 定义store
/* 
  store的名字
    useXxxStore
    student -> useStudentStore
    cart -> useCartStore
  defineStore()
    - 参数:
        id store的唯一表示,不能重复
        {} store配置
    - 返回值:函数
   
  概念:
    pinia实例 createPinia()
    useXxx函数 defineStore()
    store实例(响应式对象) use函数

  defineStore的配置对象:
    选项式API(推荐)
      - 配置对象
      - 选项:
          state
            - state用来配置store中存储的数据
            - 这些数据可以直接通过store实例访问
            - 需要一个函数作为值,函数需要返回一个对象,
                对象中的属性就会成为store实例中存储的状态

          getters
            - getters就相当于Vue中的计算属性
            - 需要一个对象作为值,对象中通过方法来设置计算属性,
                方法中可以通过this来访问当前的store实例
            - 计算属性也可以直接通过store实例来访问

          actions
            - actions相当于组件中定义的方法,可以在方法中定义一些操作的逻辑
            - actions中的定义的方法,可以通过store实例直接调用
            - 它也需要一个对象做为值,对象中的方法就是最终的函数
                在方法中可以通过this来访问store实例
    组合式API

*/
export const useStudentStore = defineStore("student", {
  state: () => ({
    name: "孙悟空",
    age: 18,
    gender: "男",
    weapons: ["金箍棒", "三根毛"],
  }),
  getters: {
    doubleAge() {
      return this.age * 2
    },
    titleName: (state) => "Mr." + state.name,
  },
  actions: {
    increase() {
      console.log(11111)

      this.age++

      return "哈哈哈"
    },

    setAge(age) {
      this.age = age
    },
  },
})

2.6 pinia插件

<script setup>
// App.vue
import { ref } from "vue"
import Box from "./components/Box.vue"
import { useHelloStore, useStudentStore, useWorldStore } from "./store/store"

// 通过调用函数,获取store中的存储的数据
const stu = useStudentStore()
const hello = useHelloStore()
const world = useWorldStore()
const show = ref(true)
</script>

<template>
  <h1>Hello Vue - {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}</h1>
  <h2>{{ stu.doubleAge }} -- {{ stu.titleName }}</h2>
  <button type="button" @click="stu.age++">点我一下</button>
  -
  <button type="button" @click="stu.increase()">increase</button>
  -
  <button type="button" @click="stu.setAge(11)">setAge</button>
  <hr />
  <button @click="show = !show">切换</button>
  <Box v-if="show"></Box>
  <!-- 
    Pinia的使用
      1.安装 npm i pinia
      2.在应用实例中注册pinia
        const pinia = createPinia()
        app.use(pinia)
      3.创建store
        store的名字
          useXxxStore
          student -> useStudentStore
          cart -> useCartStore
        defineStore()
          - 参数:
              id store的唯一表示,不能重复
              {} store配置
          - 返回值:函数
      4.使用store
          const stu = useStudentStore()

   -->
</template>

<style scoped></style>
<script setup>
// Box.vue
import { useStudentStore } from "@/store/store"
import { toRefs } from "vue"
import { storeToRefs } from "pinia"

const stu = useStudentStore()
const { name, age, gender } = storeToRefs(useStudentStore())
/* 
  store实例
    - 通过use函数返回的对象就是store实例
    - 通过store实例可以完成对其中的响应式状态的各种操作
    - store实例就是一个响应式对象
    - 作用:
      1.可以访问其中的state、getters、actions
      2.可以通过storeToRefs()来完成对store实例的解构
      3.可以直接通过它来修改state
      4.也可以通过$patch()来修改state
      5.可以通过$reset()来重置state
      6.$id、$state
      7.订阅state、action
        stu.$subscribe() -> 订阅state,订阅是和组件绑定的
        stu.$onAction() -> 订阅action

*/
stu.$onAction(({ name, store, args, after, onError }) => {
  /* 
    name 触发的action
    store store实例
    args action的参数
    after 设置回调函数
    onError action出错时调用的回调函数

  */
  console.log("action执行前调用的", name)

  after((result) => {
    console.log("action执行后调用的", name, result)
  })

  // 回调函数,会在action前执行
  // console.log("store中的方法调用了~~~~")
})
</script>

<template>
  <h2>Box</h2>
  <h3>
    {{ stu.name }} -- {{ stu.age }} -- {{ stu.gender }}-- {{ stu.weapons }}
  </h3>
  <h3>{{ name }} -- {{ age }} -- {{ gender }}</h3>
  <button @click="stu.name = '猪八戒'">修改1</button>
  -
  <button @click="stu.$patch({ age: 888 })">修改2</button>
  -
  <button @click="stu.weapons.push('大师剑')">修改3</button>
  -
  <button @click="stu.$patch({ weapons: [...stu.weapons, '大师剑'] })">
    修改4
  </button>
  -
  <button @click="stu.$patch((state) => state.weapons.push('驱魔剑'))">
    修改5
  </button>
  -
  <button @click="stu.$state = { name: '沙和尚' }">修改6</button>
  -
  <button @click="stu.$reset()">重置</button>

  -
  <!-- <button @click="stop">取消订阅</button> -->
</template>

<style scoped></style>
//main.js
import { createApp } from "vue"
import App from "@/App.vue"
import { createPinia } from "pinia"
// import Box from "@/components/Box.vue"

// 创建应用实例
/* 
  createApp()
    - 用来创建一个应用实例
    - 参数:
        需要一个根组件作为参数
*/
const app = createApp(App)

// 创建pinia的实例
const pinia = createPinia()
// 将它注册为应用的插件
app.use(pinia)

/* 
  pinia插件
    - 插件实际就是一个函数,可以用来统一对store做一些操作
    - 插件会在store实例第一次初始化时调用
    - 通过插件可以统一为store进行配置
    - 插件本身就是一个函数,函数可以设置一个对象作为返回值,
        对象中的属性将会自动的添加到store实例
        回调函数的参数:
          对象:
            store
              - store实例
            app
              - 应用实例
            pinia
              - pinia实例
            options
              - 选项,配置对象
*/
pinia.use(({ store, app, pinia, options }) => {
  console.log(options)

  if (store.$id === "student") {
    store.$subscribe(() => {
      console.log("student发生变化了!!!")
    })
  }

  return {
    key: "哈哈哈哈哈哈",
  }
})

// app.config.globalProperties.hello = "哈哈哈"

// app.component("Box", Box)

app.provide("hello", "通过应用实例提供的数据")

// 将应用实例挂载到页面中
app.mount("#app")
// /store/store.js
import { defineStore } from "pinia"
import { computed, reactive, ref } from "vue"

// 定义store
/* 
  store的名字
    useXxxStore
    student -> useStudentStore
    cart -> useCartStore
  defineStore()
    - 参数:
        id store的唯一表示,不能重复
        {} store配置
    - 返回值:函数
   
  概念:
    pinia实例 createPinia()
    useXxx函数 defineStore()
    store实例(响应式对象) use函数

  defineStore的配置对象:
    选项式API(推荐)
      - 配置对象
      - 选项:
          state
            - state用来配置store中存储的数据
            - 这些数据可以直接通过store实例访问
            - 需要一个函数作为值,函数需要返回一个对象,
                对象中的属性就会成为store实例中存储的状态

          getters
            - getters就相当于Vue中的计算属性
            - 需要一个对象作为值,对象中通过方法来设置计算属性,
                方法中可以通过this来访问当前的store实例
            - 计算属性也可以直接通过store实例来访问

          actions
            - actions相当于组件中定义的方法,可以在方法中定义一些操作的逻辑
            - actions中的定义的方法,可以通过store实例直接调用
            - 它也需要一个对象做为值,对象中的方法就是最终的函数
                在方法中可以通过this来访问store实例
    组合式API

*/
export const useStudentStore = defineStore("student", {
  state: () => ({
    name: "孙悟空",
    age: 18,
    gender: "男",
    weapons: ["金箍棒", "三根毛"],
  }),
  getters: {
    doubleAge() {
      return this.age * 2
    },
    titleName: (state) => "Mr." + state.name,
  },
  actions: {
    increase() {
      console.log(11111)

      this.age++

      return "哈哈哈"
    },

    setAge(age) {
      this.age = age
    },
  },
})


export const useHelloStore = defineStore("hello", {})

export const useWorldStore = defineStore("world", {})
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容