vue3-javasript

60 阅读7分钟

创建项目

node版本建议16

image.png

目录

image.png

setup语法糖

image.png

reactive

image.png

ref

image.png

image.png

computed

image.png

image.png

image.png

watch

image.png

image.png

image.png

image.png

deep

image.png

注意:建议不开启deep ,避免性能消耗

image.png

总结

image.png

生命周期函数

image.png

image.png

image.png

props 父传子

image.png

image.png

父组件
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive} from 'vue'


let  name=ref("小飞")

let obj =reactive({
   sex:'女',
   age:20

})
</script>
   


<template>
 
 <HelloWorld :msg="name" :o="obj"/>


 
</template>
子组件
<script setup>
// 对象写法
//  let props= defineProps({
//   msg: {
//     type: String,
//     required: true,
//      default:'默认值',
  //      validator(){
  //          return ...
//   },
   //     自定义类型校验函数
 //   o:{
  //      type:Object,
 //    }
 

// }) 


//对象可以简写成
defineProps({
  msg:String,
  o:Object
})

// 数组写法
let props=defineProps(['msg','o']) 




console.log(props);
</script>

<template>
    <h1 class="green">{{ msg }}</h1>
    <h1 class="green">{{ o.sex }}</h1>
    <h1 class="green">{{ o.age}}</h1>
    
</template>

子组件用defineProps接收,接收到的是基本类型,模版上可以直接写,如果是对象,加点

注意,接收到的数据一般是改不了,也不建议改,

- Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。

image.png

传参可以使用陀峰式也可以-的形式,例如:max-length , maxLength

script要拿这个数据,props.属性

子传父

image.png

ref

image.png

image.png

image.png

defineExpose()

image.png

image.png

image.png

image.png

v-show v-if

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

const isShow = ref(true)
</script>
<template>
    <h1>App组件</h1>
    <button @click="isShow = !isShow">切换</button>

    
      v-show 可以根据值来决定元素是否显示(通过display来切换元素的显示状态)
      v-if 可以根据表达式的值来决定是否显示元素(会直接将元素删除)
        - v-show通过css来切换组件的显示与否,切换时不会涉及到组件的重新渲染
            切换的性能比较高。
            
            但是初始化时,需要对所有组件进行初始化(即使组件暂时不显示)
            所以它的初始化的性能要差一些!

        - v-if通过删除添加元素的方式来切换元素的显示,切换时反复的渲染组件,
            切换的性能比较差。

            v-if只会初始化需要用到的组件,所以它的初始化性能比较好

            v-if可以和 v-else-if 和 v-else结合使用

            v-if可以配合template使用  --template在页面中不显示,


    -->
    <!-- <div v-if="isShow">
        <h2>我是if中的内容</h2>
    </div>
    <div v-else>
        <h2>我是else中的内容!</h2>
    </div> -->

    <template v-if="isShow">
        <h2>我是一个h2</h2>
        <h3>我是h3</h3>
    </template>
</template>

动态组件

<template>
  
  
    <component is="div">我是DIV标签</component>   
    以上代码会解析为div标签
    
     <!-- 
        component 是一个动态组件
            - component最终以什么标签呈现由is属性决定
    -->
</template>
<script setup>
import { ref } from "vue"
import A from "./components/A.vue"
import B from "./components/B.vue"

const isShow = ref(true)
</script>
<template>

    <button @click="isShow = !isShow">切换</button>
    <component :is="isShow ? A : B"></component>
    
    
</template>

is后面的属性可以是组件名,

v-bind()


<script setup>


const color="rgb(247,169,1)"


</script>
   


<template>
 
 <h1>我是H1标签</h1>


</template>

<style scoped>
   h1{
      background-color:v-bind(color);
   }
</style>

可以在srcipt定义一个变量, style用v-bind()来绑定, 这样可以动态的改变样式

<script setup>
import { ref } from "vue"
const imgPath = ref("/images/messi.png")
const changeImg = () => {
    imgPath.value = "/images/neymar.png"
}
const attrs = {
    id: "box1",
    class: "hello"
}

const attrName = "title"
const attrValue = "这是一个title属性"

const isDisabled = ""
</script>

<template>
    <
      当我们需要为标签动态的设置属性时,需要使用v-bind指令,
        v-bind可以简写为 :
      当我们为一个布尔值设置属性时,
        如果值为true,则元素上有该属性(转换后为true,也算true)
        如果值为false,则元素没有该属性(转换后为false,也算false)
        特殊情况:"" 空串,在这里会被当成真值


  -->
    <!-- <button @click="changeImg">切换图片</button> -->
    <img :[attrName]="attrValue" :src="imgPath" alt="梅西" />
    <div :="attrs"></div>

    <input type="text" :disabled="isDisabled" />
</template>

image.png

attrs的标签,id和hello属性都有

模版的基本语法

<script setup>
const msg = "我爱vue"
const html = `<h2>我是一段html代码</h2>`


</script>

<template>
    <!-- 
    - 在模板中,可以直接访问到组件中声明的变量
    - 除了组件中的变量外,vue也为我们提供了一些全局对象可以访问:
        比如:DateMathRegExp ...
        除此之外,也可以通过app对象来向vue中添加一些全局变量
        app.config.globalProperties
    - 使用插值(双大括号),只能使用表达式
        表达式,就是有返回值的语句
    - 插值实际上就是在修改元素的textContent,
        如果内容中含有标签,标签会被转义显示,不会作为标签生效

    指令:
      - 指令模板中为标签设置的一些特殊属性,它可以用来设置标签如何显示内容
      - 指令使用v-开头
        v-text 将表达式的值作为元素的textContent插入,作用同{{}}
          使用指令时,不需要通过{{}}来指定表达式
        v-html 将表达式的值作为元素的innerHTML插入,有xss共计的风险


  -->
    <h1>{{ "hello" + "world" }}</h1>
    <div>{{ html }}</div>
    <div v-text="html"></div>
    <div v-html="html"></div>

在main.js文件可以设置全局的属性

image.png

添加方法时,需要用bind改变this指向,因为这里是点alert 不是指向全局

  全局定义了,可以直接使用
   <h1>{{ hello }}</h1>
  <div>{{ alert(123)}}</div>  

动态样式

<script setup>

const arr = ["box1", "box2", "box3"]
const arr2 = [{ box1: true }, { box2: false }, { box3: true }]
const style = {
    color: "red",
    backgroundColor: "#bfa"
}
</script>

<template>
    
    <div :class="arr">我是第一个</div>
    <div :class="arr2">我是第二个</div>
    <div :style='style'>我是第三个</div>
</template>

<style scoped>
  .box1{
    color:aqua
  }
  .box2{
    background-color:aqua;
  }
  .box3{
    border: 1px solid red;
  }
</style>

image.png

class 数组是全部使用,对象形式,里面的布尔值决定使用不使用这个样式,

style样式,在script 里直接定义样式,

v-for 与v-if

同时使用 v-if 和 v-for 是不推荐的**,因为这样二者的优先级不明显。

当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

    <!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):

   <template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

scoped

image.png

image.png

image.png

image.png

注意,随机生成的属性,子组件的根元素,会受到父组件的样式影响,===只限于子组件,是单根组件,如果是多根组件,则不会受影响


<script setup>
    import A from './components/A.vue'
</script>

<template>
    <div class="app">
      <h1>我是父组件h1</h1>
      <A></A>
    </div>
</template>
  

<style scoped>
  .app  h1{
      color: red;
    }
</style>
<script setup>

</script>


<template>
   
 <h1>我是子组件h1</h1>
      
        
</template>
<style scoped>
</style>

image.png


<script setup>
    import A from './components/A.vue'
</script>

<template>
    <div class="app">
      <h1>我是父组件h1</h1>
      <A></A>
    </div>
</template>
  

<style scoped>
 .app :deep(h1){
      color: red;
    }
</style>

<script setup>

</script>


<template>
   
  <div>
    <h1>我是子组件h1</h1>
  </div>
      
        
</template>
<style scoped>
   
</style>

当子组件h1不是根组件,这时。就要使用:deep() 方法来 实现

插槽

插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。

image.png

image.png

默认插槽

 子组件
<template>
    <div>
       <slot></slot>   在哪里渲染
    </div>
</template>

在父组件给这个插槽填充内容

        <Dialog> 
           <template v-slot>
               1233
           </template>
        </Dialog>
        
        

还可以写成这样

 <Dialog> 12333  </Dialog>

具名插槽

具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中

子组件
    <div>
        <slot name="header"></slot>  
        <slot></slot>
 
        <slot name="footer"></slot>
    </div>
    
    
    有name属性的是具名插槽

父组件使用需对应名称

        <Dialog>
            <template v-slot:header>
               <div>1</div>
           </template>
           <template v-slot>
               <div>2</div>
           </template>
           <template v-slot:footer>
               <div>3</div>
           </template>
        </Dialog>

插槽简写

        <Dialog>
            <template #header>
               <div>1</div>
           </template>
           <template #default>
               <div>2</div>
           </template>
           <template #footer>
               <div>3</div>
           </template>
        </Dialog>

默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容

 子组件
    <div>
        <slot> 我是默认内容</slot>
       
    </div>
父组件
        <Dialog>
           
        </Dialog>

渲染作用域 插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:

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

import HelloWorld from './components/HelloWorld.vue'

 const  num=ref(0)

</script>
   

<template>
 

<!-- 父组件 -->
 <hello-world>
    <template v-slot:a>
       {{ num }}  
    </template>
 </hello-world>
 
 
</template>

<template>
    <!-- 子组件 -->
 
    <slot name="a"></slot>
</template>

插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:

父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

动态插槽

插槽可以是一个变量名

        <Dialog>
            <template #[name]>
                <div>
                    23
                </div>
            </template>
        </Dialog>
    
    const name = ref('header')

作用域插槽

image.png

image.png

<script setup>

import SlotDemo from "./components/SlotDemo.vue"


</script>
<template>
    <SlotDemo>
        
        <template v-slot:s2="{ stuName, age, gender }">  解构
            <div >{{stuName, age, gender}}</div>
        </template>
    </SlotDemo>
</template>
<script setup>
const stuName = "孙悟空"
const age = 18
const gender = "男"

</script>
<template>
    <div>
        slotDemo组件
        <div>
            <slot
                :gender="gender"
                :age="age"
                :stuName="stuName"
            ></slot>
        </div>
    </div>
</template>

事件处理

  1. 内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与 onclick 类似)。

内联事件处理器通常用于简单场景,例如

data() {
  return {
    count: 0
  }
}
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

方法事件处理器

<script setup>

import {ref}  from 'vue'

 const count= ref(0)
 function gosearch(e){
    count.value++
    console.log(e);
 }


</script>
   
 
<template>
 
 <div>{{ count }}</div>
 <button @click="gosearch">点我加1</button>
 
 
</template>

不传参时,默认vue帮我们传入了一个事件对象event

image.png

<script setup>

import {ref}  from 'vue'

 const count= ref(0)
 function gosearch(a,e){
    count.value++
    console.log(a,e);
 }


</script>
   
 
<template>
 
 <div>{{ count }}</div>
 <button @click="gosearch('123',$event)">点我加1</button>
 
 
</template>

当我们手动传参数,要使用事件对象,可以直接传$event ,不传的话,默认是没有

image.png

事件修饰符

   .stop 停止事件冒泡
    
    .capture 在捕获阶段触发事件
    
    .prevent 取消默认行为
    
    .self 只有事件由自身触发时才会有效
    
    .once 绑定一个一次性的事件
    
    .passive 主要用于提升滚动事件的性能

透传属性

<script setup>
import C from "./components/C.vue"


function showMsg() {
    alert("hello")
}
</script>

<template>
   
    <C class="box2" style="color: red" @click="showMsg"></C>
    
</template>
<script setup>
</script>

<template>
    <h2 class="box3" style="font-size: 60px">我是组件C</h2>

   
</template>


/* 透传属性

    - 在组件上设置属性,会自动传递给组件的根元素
    - 这样一来可以方便我们在父组件中为子组件来设置属性
    - 透传会发生在没有被声明为props和emit的属性上
    - 自动的透传只适用单根组件

*/

当子组件,是单根组件时,属性默认会传递过来

 <!-- 
        在模板中,可以通过$attrs来访问透传过来的属性,
            可以手动指定透传过来的属性要添加到哪些元素
    -->
    <!-- {{ $attrs }} -->
    
    

$attrs 是一个对象,里面有父组件的属性 image.png




<template>
   

     <h2 class="box3" :class="$attrs.class" style="font-size: 60px">
        我是组件C
    </h2> 
     <h3 :class="$attrs.class" :style="$attrs.style">我也是组件C</h3> 

   
</template>

当子组件不是单根组件时,默认的就会失效,可以通过$attrs 来手动设置

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


   
const attrs = useAttrs()

console.log(attrs)
</script>

在script中,可以通过useAttrs()来获取透传过来的属性

<script>
export default {
    inheritAttrs: false
}
</script>

当我想取消默认传过来的属性,可以多设置一层script 标签,设置 inheritAttrs: false,

双向绑定

<script setup>
import { ref } from "vue"
const text = ref("")
const bool = ref("是")
const hobbies = ref([])
const gender = ref("女")
const friend = ref("猪八戒")

function submitHandler() {
    console.log(text.value)
    // 将text提交给服务器,在根据服务器返回的数据做后续的操作
}
</script>

<template>
    <h1>Hello Vue</h1>
    <form @submit.prevent="submitHandler">
         
        在vue中,为我们提供了v-model了可以快速完成表单的双向数据绑定

       
        <div>信息:<input type="text" v-model="text" /></div>
        <div>
            是否:<input
                type="checkbox"
                v-model="bool"
                true-value="是"
                false-value="否"
            />
        </div>
        <div>
            爱好:
            <input
                v-model="hobbies"
                type="checkbox"
                name="hobby"
                value="足球"
            />足球
            <input
                v-model="hobbies"
                type="checkbox"
                name="hobby"
                value="篮球"
            />篮球
            <input
                v-model="hobbies"
                type="checkbox"
                name="hobby"
                value="羽毛球"
            />羽毛球
            <input
                v-model="hobbies"
                type="checkbox"
                name="hobby"
                value="乒乓球"
            />乒乓球
        </div>
        <div>
            性别:
            <input v-model="gender" type="radio" name="gender" value="男" /><input v-model="gender" type="radio" name="gender" value="女" /></div>
        <div>
            朋友:
            <select v-model="friend">
                <option disabled value="">请选择你的好朋友...</option>
                <option>孙悟空</option>
                <option>猪八戒</option>
                <option>沙和尚</option>
            </select>
        </div>
        <div>
            <button>提交</button>
        </div>
    </form>
</template>

select框 如果 v-model 表达式的初始值不匹配任何一个选择项,<select> 元素会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。因此,我们建议提供一个空值的禁用选项,如上面的例子所示。

多选 (值绑定到一个数组)

image.png

修饰符

         .lazy 使用change来处理数据
         
        .trim 去除前后的空格
        .number 将数据转换为数值

自定义事件

方法一

父组件

<script setup>
import StudentList from "./components/StudentList.vue"

import { reactive } from "vue";


const STU_ARR = reactive([
    {
        id: 1,
        name: "孙悟空",
        age: 18,
        gender: "男",
        address: "花果山"
    },
    {
        id: 2,
        name: "猪八戒",
        age: 28,
        gender: "男",
        address: "高老庄"
    },
    {
        id: 3,
        name: "沙和尚",
        age: 38,
        gender: "男",
        address: "流沙河"
    },
    {
        id: 4,
        name: "唐僧",
        age: 16,
        gender: "男",
        address: "女儿国"
    }
])
function delStudentByIndex(index){
    STU_ARR.splice(index,1)
}

</script>

<template>
    <!-- 可以将组件中的方法以自定义事件的形式发送给其他的组件 -->
    <StudentList :stu="STU_ARR"  @delStu="delStudentByIndex"></StudentList> 
    
</template>

子组件

<script setup>
const porps=defineProps(['stu'])

</script>

<template>
    
    <table>
        <caption>
            学生列表
        </caption>
        <thead>
            <tr>
                <th>学号</th>
                <th>姓名</th>
                <th>年龄</th>
                <th>性别</th>
                <th>住址</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
        <tr v-for="(s,index) in stu" :key="s.id">
            <td>{{ s.id }}</td>
            <td>{{ s.name }}</td>
            <td>{{ s.age }}</td>
            <td>{{ s.gender }}</td>
            <td>{{ s.address }}</td>
            <td>
                <!-- 在模板中可以通过$emit()来触发自定义事件 -->
                <a href="#" @click.prevent="$emit('delStu', index)">删除</a>
               
            </td>
        </tr>
    </tbody>
    </table>


</template>

props接收到的传过来的数据,一般不修改, 删除数据,需要通知父组件去删改

方法二:直接props传了一个函数,

<script setup>
import StudentList from "./components/StudentList.vue"

import { reactive } from "vue";


const STU_ARR = reactive([
    {
        id: 1,
        name: "孙悟空",
        age: 18,
        gender: "男",
        address: "花果山"
    },
    {
        id: 2,
        name: "猪八戒",
        age: 28,
        gender: "男",
        address: "高老庄"
    },
    {
        id: 3,
        name: "沙和尚",
        age: 38,
        gender: "男",
        address: "流沙河"
    },
    {
        id: 4,
        name: "唐僧",
        age: 16,
        gender: "男",
        address: "女儿国"
    }
])
function delStudentByIndex(index){
    STU_ARR.splice(index,1)
}

</script>

<template>
    <!-- 可以将组件中的方法以自定义事件的形式发送给其他的组件 -->
    <StudentList :stu="STU_ARR"  :fn="delStudentByIndex"></StudentList> 
    
</template>

子组件

<script setup>
const porps=defineProps(['stu',"fn"])
    // 定义一个删除学生的方法
const delStuHandler = (index) => {
    if (confirm("该操作不可恢复,请确认!")) {
       porps.fn(index)
       // 这里执行父组件的删除函数
       
    }
}
   
</script>

<template>
    
    <table>
        <caption>
            学生列表
        </caption>
        <thead>
            <tr>
                <th>学号</th>
                <th>姓名</th>
                <th>年龄</th>
                <th>性别</th>
                <th>住址</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
        <tr v-for="(s,index) in stu" :key="s.id">
            <td>{{ s.id }}</td>
            <td>{{ s.name }}</td>
            <td>{{ s.age }}</td>
            <td>{{ s.gender }}</td>
            <td>{{ s.address }}</td>
            <td>
               
                <a href="#" @click.prevent="delStuHandler(index)">删除</a>
            </td>
        </tr>
    </tbody>
    </table>


</template>

方法三

<script setup>
import StudentList from "./components/StudentList.vue"

import { reactive } from "vue";


const STU_ARR = reactive([
    {
        id: 1,
        name: "孙悟空",
        age: 18,
        gender: "男",
        address: "花果山"
    },
    {
        id: 2,
        name: "猪八戒",
        age: 28,
        gender: "男",
        address: "高老庄"
    },
    {
        id: 3,
        name: "沙和尚",
        age: 38,
        gender: "男",
        address: "流沙河"
    },
    {
        id: 4,
        name: "唐僧",
        age: 16,
        gender: "男",
        address: "女儿国"
    }
])
function delStudentByIndex(index){
    STU_ARR.splice(index,1)
}

</script>

<template>
    <!-- 可以将组件中的方法以自定义事件的形式发送给其他的组件 -->
    <StudentList :stu="STU_ARR"  @destu="delStudentByIndex"></StudentList> 
    
</template>

子组件

<script setup>
const porps=defineProps(['stu',"fn"])
//用defineEmits接收
const emits=defineEmits(['destu'])

    // 定义一个删除学生的方法
const delStuHandler = (index) => {
    if (confirm("该操作不可恢复,请确认!")) {
        
        emits("destu", index)
        //触发
       
    }
}
   
</script>

<template>
    
    <table>
        <caption>
            学生列表
        </caption>
        <thead>
            <tr>
                <th>学号</th>
                <th>姓名</th>
                <th>年龄</th>
                <th>性别</th>
                <th>住址</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
        <tr v-for="(s,index) in stu" :key="s.id">
            <td>{{ s.id }}</td>
            <td>{{ s.name }}</td>
            <td>{{ s.age }}</td>
            <td>{{ s.gender }}</td>
            <td>{{ s.address }}</td>
            <td>
                <a href="#" @click.prevent="delStuHandler(index)">删除</a>
            </td>
        </tr>
    </tbody>
    </table>


</template>

增加元素

app组件

<script setup>
import StudentList from "./components/StudentList.vue"
import StudentForm from './components/StudentForm.vue'

import { reactive } from "vue";


const STU_ARR = reactive([
    {
        id: 1,
        name: "孙悟空",
        age: 18,
        gender: "男",
        address: "花果山"
    },
    {
        id: 2,
        name: "猪八戒",
        age: 28,
        gender: "男",
        address: "高老庄"
    },
    {
        id: 3,
        name: "沙和尚",
        age: 38,
        gender: "男",
        address: "流沙河"
    },
    {
        id: 4,
        name: "唐僧",
        age: 16,
        gender: "男",
        address: "女儿国"
    }
])
// 删除
function delStudentByIndex(index){
    STU_ARR.splice(index,1)
}
// 添加学生
function addstudent(newStu){
    let addid=STU_ARR.length+1

    newStu.id=addid
    STU_ARR.push(newStu)
}

</script>

<template>
    <!-- 可以将组件中的方法以自定义事件的形式发送给其他的组件 -->
    <StudentList :stu="STU_ARR"  @destu="delStudentByIndex"></StudentList> 
    
    <hr />
    <StudentForm @addstu="addstudent"></StudentForm>
</template>

StudentForm组件,

<script setup>
import { reactive } from 'vue';

const  emits=defineEmits(['addstu'])

// 创建一个ref来存储新的学生信息
const newStu = reactive({
    name: "",
    age: 1,
    gender: "男",
    address: ""
})
const submitHandler = () => {
   
//  newStu是 响应式数据,需要复制到新的空对象  
    emits('addstu',{...newStu})
   
    newStu.name = ""
    newStu.age = 1
    newStu.gender = "男"
    newStu.address = ""
}
</script>
<template>
    <form @submit.prevent="submitHandler">
        <div>姓名 <input type="text" v-model="newStu.name" /></div>

        <div>年龄 <input type="number" min="1" v-model="newStu.age" /></div>

        <div>
            性别
            <input
                v-model="newStu.gender"
                type="radio"
                name="gender"
                value="男"
            /><input
                v-model="newStu.gender"
                type="radio"
                name="gender"
                value="女"
            /></div>

        <div>
            住址
            <input v-model="newStu.address" type="text" />
        </div>

        <div>
            <button>添加</button>
        </div>
    </form>
</template>

依赖注入

image.png

image.png

image.png

image.png 依赖注入

    - 通过依赖注入,可以跨域多层组件向其他的组件传递数据
    - 步骤:
        1. 设置依赖(provide) provide(name, value)
        2. 注入数据 (inject) const value = inject(name, default)
        
        
        能够使用, 2个组件,必须是后代关系( 比如父子或爷孙等)
APP组件
<script setup>
import StudentList from "./components/StudentList.vue"
import StudentForm from './components/StudentForm.vue'

import { reactive ,provide} from "vue";


const STU_ARR = reactive([
    {
        id: 1,
        name: "孙悟空",
        age: 18,
        gender: "男",
        address: "花果山"
    },
    {
        id: 2,
        name: "猪八戒",
        age: 28,
        gender: "男",
        address: "高老庄"
    },
    {
        id: 3,
        name: "沙和尚",
        age: 38,
        gender: "男",
        address: "流沙河"
    },
    {
        id: 4,
        name: "唐僧",
        age: 16,
        gender: "男",
        address: "女儿国"
    }
])

// 设置依辣

provide('add',addstudent)  
// 删除


function delStudentByIndex(index){
    STU_ARR.splice(index,1)
}
// 添加学生
function addstudent(newStu){
    let addid=STU_ARR.length+1

    newStu.id=addid
    STU_ARR.push(newStu)
}

</script>

<template>
    <!-- 可以将组件中的方法以自定义事件的形式发送给其他的组件 -->
    <StudentList :stu="STU_ARR"  @destu="delStudentByIndex"></StudentList> 
    
    <hr />
    <StudentForm ></StudentForm>
</template>
<script setup>
import { reactive,inject } from 'vue';
//注入数据
const addstudent=inject('add')  


// 创建一个ref来存储新的学生信息
const newStu = reactive({
    name: "",
    age: 1,
    gender: "男",
    address: ""
})


const submitHandler = () => {
   
    addstudent({...newStu})
    //可以直接使用注册的函数
   
    newStu.name = ""
    newStu.age = 1
    newStu.gender = "男"
    newStu.address = ""
}
</script>
<template>
    <form @submit.prevent="submitHandler">
        <div>姓名 <input type="text" v-model="newStu.name" /></div>

        <div>年龄 <input type="number" min="1" v-model="newStu.age" /></div>

        <div>
            性别
            <input
                v-model="newStu.gender"
                type="radio"
                name="gender"
                value="男"
            /><input
                v-model="newStu.gender"
                type="radio"
                name="gender"
                value="女"
            /></div>

        <div>
            住址
            <input v-model="newStu.address" type="text" />
        </div>

        <div>
            <button>添加</button>
        </div>
    </form>
</template>

image.png

注意,假设 爷组件 有 设置依辣,父组件也有设置相同的依辣,那么孙(子),会优先使用父组件的数据

pinia

用你喜欢的包管理器安装 pinia

npm install pinia


main.js文件
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

Store 是什么?

Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念stategetter 和 action,我们可以假设这些概念相当于组件中的 data、 computed 和 methods

image.png

在src下 配置stroe文件

与 Vue 的选项式 API 类似,我们也可以传入一个带有 stateactions 与 getters 属性的 Option 对象

你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

上面是Option 对象写法, 还可以写成setup函数(组合api)

image.png

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

使用 Store

<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
</script>


<template>
     <div>{{ store.count }}</div>
     <div>{{ store.double }}</div>
     <button @click="store.addnumber">点我加1</button>
  
</template>

优化


import { storeToRefs } from "pinia";  引入storeToRefs



const  { count ,double } =storeToRefs(store)  只能解构属性 

const { addnumber} =store   方法可以直接解构


<div>{{ count }}</div>
<div>{{ double }}</div>
<button @click="addnumber">点我加1</button>

不用storeToRefs解构,直接解构,解构出来的属性不具有响应性

重置 state 你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

const store = useStore()

store.$reset()

变更 state

方法一:直接修改,不推荐

state:()=>({ count:0 ,name:"张三"}),

 <div>{{ name }}</div>
 <button @click="()=>name='李四'">修改name属性</button>

方法二: 通过$patch ( 此方法,是替换state里面的属性,没有修改的不替换,对数组不友好)

state:()=>({ 
        count:0 ,
        name:"张三",
        list:['a','b','c']
        
    }),
    
    
     <div>{{ list }}</div>
     <div>{{ name }}</div>
     <button @click="hander">修改name和list属性</button>
     function hander(){
    store.$patch({
        name:"李四",
        list:['d']
        
    })
}

修改前

image.png

修改后

image.png

有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)都需要你创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。

方法三:$patch` 方法也接受一个函数来组合这种难以用补丁对象实现的变更。


store.$patch((state) => {
  state.list.push('d')
  state.name = '李四'
})

两种变更 store 方法的主要区别是,$patch() 允许你将多个变更归入 devtools 的同一个条目中。同时请注意,直接修改 state$patch() 也会出现在 devtools 中

订阅state

store的订阅

    - 当store中的state发生变化时,做一些响应的操作
    - store.$subscribe(函数, 配置对象)
stuStore.$subscribe(
    (mutation, state) => {
        // mutation 表示修改的信息
        // console.log(mutation.events)
      
        // console.log("state发生变化了", state)
        // 使用订阅时不要在回调函数中直接修改state,不然会被递归
        // state.age++
    },
    { detached: true } 
)

{ detached: true } 第二个参数,此订阅器即便在组件卸载之后仍会被保留

通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 和 cartStore.$id 一样
  mutation.storeId // 'cart'
  // 只有 mutation.type === 'patch object'的情况下才可用
  mutation.payload // 传递给 cartStore.$patch() 的补丁对象。

  // 每当状态发生变化时,将整个 state 持久化到本地存储。
  localStorage.setItem('cart', JSON.stringify(state))
})

action方法:


 actions:{
        addnumber( ...args){
        //   console.log(this);
        // 指向了这个store实例对象
        console.log(args);

        this.count++
        }
    }

action里面的方法,默认传入了一个事件对象,是拿不到state,可以用this

image.png



 $onAction 用来订阅action的调用 ()====》action里面定义的方法被触发时调用
 
stuStore.$onAction(({ name, store, args, after, onError }) => {
    
      

    after(() => {
        console.log(name + "成功执行!")
    })

    onError((err) => {
        console.log(name + "执行失败!", err)
    })
})
    name 调用的action的名字
    store store的实例
    args action接收到的参数
    after() 可以设置一个回调函数,函数会在action成功调用后触发
    onError() 可以设置一个回调函数,函数会在action调用失败后触发

计算属性 getters

 // 计算属性
    getters: {
        // double:state=>state.count*2,
        double(){
           return this.count*2
        }
    },

补充:字体图标Remix Icon - Open source icon library

怎么使用呢

image.png

image.png

这样就可以使用了,想用哪个,直接复制相应代码 image.png

自定义指令