Vue3中双向数据绑定与Pinia实践+JS数据引用的循环修改问题

news/2024/12/2 12:48:18/

Vue3 + Pinia

VUE3虽然出了很久了,但是很少深入研究,目前项目上遇到了一些问题,所以做个Note解决一下疑问:

  1. v-bind/v-model怎么与Pinia进行结合
  2. Object/Array数据大量处理时,为何有的修改不生效
  3. 组合API与选项API选择 (TS不考虑)
  4. This指针问题

生命周期
https://cn.vuejs.org/assets/lifecycle.16e4c08e.png

Vue3基础语法

不熟悉情况下直接选择选项式API。对TS和组合有需求的再选组合式API。

语法风格

注意,生成的项目中的示例组件使用的是组合式 API 和 <script setup>,而非选项式 API。下面是一些补充提示:

  • 推荐的 IDE 配置是 Visual Studio Code + Volar 扩展。如果使用其他编辑器,参考 IDE 支持章节。
  • 更多工具细节,包括与后端框架的整合,我们会在工具链指南进行讨论。
  • 要了解构建工具 Vite 更多背后的细节,请查看 Vite 文档。
  • 如果你选择使用 TypeScript,请阅读 TypeScript 使用指南。

选项式 API (Options API)

选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

<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>

组合式 API (Composition API)

这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。
选项式 API 是在组合式 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>

Demo

全局引入网页:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script><div id="app">{{ message }}</div><script>const { createApp } = VuecreateApp({data() {return {message: 'Hello Vue!'}}}).mount('#app')
</script>

模块化ES开发:

<!-- index.html -->
<div id="app"></div><script type="module">import { createApp } from 'vue'import MyComponent from './my-component.js'createApp(MyComponent).mount('#app')
</script>
// my-component.js
export default {data() {return { count: 0 }},template: `<div>count is {{ count }}</div>`
}

组件化开发:

// ButtonCounter.vue
<script>
export default {props: ['title'],emits: ['enlarge-text'],data() {return {count: 0}}
}
</script><template><h4>{{ title }}</h4><button @click="count++">You clicked me {{ count }} times.</button><button @click="$emit('enlarge-text')">Enlarge text</button><slot /> <!-- 这是一个插槽占位符,通过父组件传递给子组件 -->
</template>// main.vue
<script>
import ButtonCounter from './ButtonCounter.vue'export default {components: {ButtonCounter},methods:{btn_click(){},}
}
</script><template><h1>Here is a child component!</h1><ButtonCounter title="My journey with Vue" @enlarge-text="btn_click" /><ButtonCounter title="abc" @enlarge-text="btn_click" />
</template>

所有 prop 默认都是可选的,除非声明了 required: true。
props的类型校验参考 https://cn.vuejs.org/guide/components/props.html#prop-validation

v-slot

<!-- <MyComponent> 的模板 -->
<div><slot :text="greetingMessage" :count="1"></slot>
</div><MyComponent v-slot="slotProps">{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<MyComponent v-slot="{ text, count }">{{ text }} {{ count }}
</MyComponent>

几个重要语法

没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。


// 默认的绑定都是单向的: vue to html
<span>数据绑定: {{ msg }}</span><p>渲染原始HTML: <span v-html="rawHtml"></span></p><div v-bind:id="dynamicId">属性绑定(v-bind:可以省略为:)</div><div class="static" :class="{ active: isActive, 'text-danger': hasError }" ></div>objectOfAttrs: {id: 'container',class: 'wrapper',href: 'href'
}
<a v-bind="objectOfAttrs">同时绑定一个元素的多个属性</a>// 每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码
<span>绑定支持表达式: {{ msg?:'abc':'123' + id }}</span>  // 动态attribute bind
<a v-bind:[attributeName]="url"> ... </a>
<a :[attributeName]="url"> ... </a>
<!-- 这会触发一个编译器警告, 用计算属性替代 -->
<a :['foo' + bar]="value"> ... </a>// on 用来监听DOM事件
<a v-on:click="doSomething"> ... </a>
<a @click="doSomething"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<a @[eventName]="doSomething"><!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">Submit
</button>// 修饰符 Modifiers, prevent会调用event.preventDefault() 
<form @submit.prevent="onSubmit">...</form><!-- Alt + Enter -->
<input @keyup.alt.enter="clear" /><!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>// v-if 可以用在template上
// 当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行。// items: [{ message: 'Foo' }, { message: 'Bar' }]
<li v-for="item in items">{{ item.message }}
</li>
<li v-for="(item, index) in items">{{ parentMessage }} - {{ index }} - {{ item.message }}
</li><li v-for="{ message } in items">{{ message }}
</li><!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">{{ message }} {{ index }}
</li>// 也可以对一个对象object使用v-for遍历所有属性
<li v-for="value in myObject">{{ value }}
</li>
<li v-for="(value, key, index) in myObject">{{ index }}. {{ key }}: {{ value }}
</li>

表单与双向绑定

<input :value="text" @input="event => text = event.target.value" />
// 与 v-model 等价
<input v-model="text" />// 双向绑定数组 checkedNames: []
// 选中后 checkedNames = ['jack','john','mike']
<div>Checked names: {{ checkedNames }}</div><input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label><input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label><input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>// 如果是单选radio, 则只会存储一个值: picked = 'one' or 'two'
<div>Picked: {{ picked }}</div><input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label><input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>// select 单选时 等同 radio效果
// <select v-model="selected" multiple> => 多选等同于checkbox效果
<div>Selected: {{ selected }}</div><select v-model="selected"><option disabled value="">Please select one</option><option value="1">A</option><option>B</option><option>C</option>
</select>// 动态绑定,pick被自动设置为first/second
<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />// v-model的修饰符:.lazy/.number/.trim 

DOM元素绑定 ref
ref 是一个特殊的 attribute,它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。

<script>
export default {mounted() {this.$refs.input.focus()}
}
</script><template><input ref="input" />
</template>

当在 v-for 中使用模板引用时,相应的引用中包含的值是一个数组:<li v-for="item in list" ref="items">

除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数:<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

特殊数据的监听

  1. 数组
    Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
  • 替换一个数组this.items = this.items.filter((item) => item.message.match(/Foo/))
  1. watch
    参考vue进行。默认是浅层watch.

JS实用小技巧

Vue3中的this

export default {data() {return {count: 0}},methods: {increment() {this.count++// 在Dom更新后被调用nextTick(() => {// 访问更新后的 DOM})}},mounted() {// 在其他方法或是生命周期中也可以调用方法this.increment()}
}

**Vue 自动为 methods 中的方法绑定了永远指向组件实例的 this。**这确保了方法在作为事件监听器或回调函数时始终保持正确的 this

你不应该在定义 methods 时使用箭头函数,因为箭头函数没有自己的 this 上下文。

箭头函数没有自己的this值,箭头函数中所使用的this来自于函数作用域链(上下文)。

const obj = {name: 'this test',// 普通函数/匿名函数func1: function(){console.log(this) // 这个this指向对象obj本身, vue帮忙做的bind},func2(){console.log(this) // 这个this指向对象obj本身}// 指针/箭头函数// 错误做法,VUE不支持// 语法不对,仅示意func3: ()=>{console.log(this) // 这个this指向父对象,windows,不是obj},// 组合func4: function(){console.log(this) // 这个this指向对象obj本身func4_1: ()=>{console.log(this) // 这个this指向父对象,func4,但func4的this指向obj,所以这里this也是obj}}// 组合+推荐方案func5: function(){  // 这里VUE做了bind处理console.log(this) // 这个this指向对象obj本身, vue帮忙做的bind(this)setTimeout(function(){console.log(this) // 这里this有问题,不指向obj// solution:// 1. 手动 setTimeout().bind(this)// 2. let self = this; 然后 self.xxxx// 3. 使用 `()=>{}` 箭头函数,不使用匿名函数}, 1000);},
}

JS foreach 陷阱

在JS中,除了基本数据类型(number,string, boolean),其他都是引用类型。所以,对object赋值,都是引用的对方,进行的浅拷贝。

所以,我们在对array进行forEach并且修改值的时候,可能发生修改了没有效果的情况。

case 1:

let a = [1, 10, 21, 33];// 错误做法
a.forEach((item, index, arr)={item += 100;  // 这种修改是无效的,因为每次的item都是一个全新的,与原来的item没关系
});// 正确做法
a.forEach( (val, index)=> a[index]+=100 );
a.forEach( (val, index, arr)=>{arr[index] += 1000; // a[index] += 1000; 也可以
});

case 2:

let a = [{b1: 1001, b2: 'xxxx', b3:[1,2,3]},{b1: 1002, b2: '123111', b3:[1,2,3]},{b1: 1003, b2: 'aaaaaaaaaa', b3:[1,2,3]},
];// forEach与手写for循环效果一样// 针对数组a
a.forEach((val, index)=>{val.b2 += "_method1";  // 因为val是object(非基本数据类型),对原来item的引用,所以可以修改
});// 针对数组b3
// 建议的做法
a.forEach((val, index)=>{val.b2 += "_method2";val.b3.forEach((v2, i2, arr)=>{// 正确做法, 2种都可以val.b3[i2] += 100;arr[i2] += 1000;// 错误做法v2 += 10000; // v2是number,不能这样修改});
});

数据扩展

注意: 数据只会展开一层,内部数据不会展开。

let a = [1,2,3];
let b = [3,4,5];console.log([...a, ...b])  // [1,2,3,3,4,5]let x = {a:1, b:true, c: '123'};
let y = {a:88, d: [1,2,3]};console.log({...a, ...b}) // {a:88, b:true, c: '123', d:[1,2,3]}
console.log({...b, ...a}) // {a:1, d:[1,2,3], b:true, c: '123'}

Pinia与双向绑定

  1. 对于value绑定的类型(input, select, options, textarea, radio, checkbox)直接使用v-model绑定
  2. 对于自定义类型,需要手动处理v-bindclick/change一类的事件,借用$event.target.value处理。
  3. 如果使用Pinia则考虑storeToRefs映射变量,或者通过action/$patch功能手动设定。

参考: https://www.45fan.com/article.php?aid=1D0cTL9M3N582fPx

import { defineStore } from 'pinia'
// 创建store,命名规则: useXxxxStore
// 参数1:store的唯一表示
// 参数2:对象,可以提供state actions getters
const useCounterStore = defineStore('counter', {state: () => {return {count: 0,}},getters: {double() {return this.count * 2},},actions: {increment() {this.count++},incrementAsync() {setTimeout(() => {this.count++}, 1000)},},
})export default useCounterStore
<script setup>
import useCounterStore from './store/counter'const counter = useCounterStore()// 如果直接从pinia中解构数据,会丢失响应式, 使用storeToRefs可以保证解构出来的数据也是响应式的
//const { count, double } = counter  // 错误,这个没有响应性,不可以这样
const { count, double } = storeToRefs(counter)  // 这个可以</script><template><h1>根组件---{{ counter.count }}</h1>  <!-- 这个也具有响应性 --><h1>响应测试: {{ count +' - '+ double }}</h1><button @click="counter.increment">加1</button><button @click="counter.incrementAsync">异步加1</button>
</template><style></style>

如果想做pinia数据持久化, 使用插件pinia-plugin-persistedstate

import { createApp } from "vue";
import App from "./App.vue";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
createApp(App).use(pinia);

自定义选项:

import { defineStore } from 'pinia'export const useStore = defineStore('main', s{state: () => {return {someState: 'hello pinia',nested: {data: 'nested pinia',},}},// 所有数据持久化// persist: true,// 持久化存储插件其他配置persist: {// 修改存储中使用的键名称,默认为当前 Store的 idkey: 'storekey',// 修改为 sessionStorage,默认为 localStoragestorage: window.sessionStorage,// 部分持久化状态的点符号路径数组,[]意味着没有状态被持久化(默认为undefined,持久化整个状态)paths: ['nested.data'],},
})

http://www.ppmy.cn/news/59205.html

相关文章

在Android应用中集成使用traceroute工具

背景知识 traceroute是一个常用于Linux系统的网络工具&#xff0c;它可显示数据包在IP网络中所经过路由的IP地址&#xff0c;理想状态下可探测本机和目标地址之间的所有路由节点。 其他操作系统中也有类似的替代品&#xff0c;实现都大同小异。一般用法如下&#xff1a; 终端…

极简爬虫通用模板

网络爬虫的一般步骤如下&#xff1a; 1、确定爬取目标&#xff1a;确定需要爬取的数据类型和来源网站。 2、制定爬取策略&#xff1a;确定爬取哪些网页、如何爬取和频率等。 3、构建爬虫程序&#xff1a;使用编程语言&#xff08;如Python&#xff09;实现爬虫程序&#xff…

Spark概述

1.Spark用来干啥的&#xff1f; Spark是一个开源的大数据处理框架&#xff0c;主要用于分布式计算和数据处理。Spark可以处理大量的数据&#xff0c;并且可以在分布式计算中进行数据处理和分析&#xff0c;具有高效性和可扩展性。Spark支持多种编程语言&#xff0c;并且可以与…

SpringCloud学习(七)——统一网关Gateway

文章目录 1. 网关介绍2. 网关搭建2.1 引入依赖2.2 创建启动类2.3 编写配置2.4 测试 3. 路由断言工厂4. 路由过滤器4.1 过滤器配置4.2 全局过滤器4.3 过滤器执行顺序 5. 跨域问题处理 1. 网关介绍 到现在&#xff0c;我们可以使用Nacos对不同的微服务进行注册并管理配置文件&am…

asp.net基于web的校园美食派送配送系统

1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和验证码&#xff0c;然后对登录进来的用户判断身份信息&#xff0c;判断是管理员用户还是普通用户。 2&#xff0e;系统用户管理&#xff1a;不管是…

大规模MIMO系统中基于CSI的卷积神经网络定位

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 论文标题&#xff1a;CSI-based Positioning in Massive MIMO systems using Convolutional Neural Networks 摘要 研究了使用大规模MIMO&#xff08;MaMIMO&#xff09;系统的信道状态信息&#xff08;CS…

MSQL知识学习07(MySQL执行计划分析)

1、什么是执行计划&#xff1f; 执行计划 是指一条 SQL 语句在经过 MySQL 查询优化器 的优化会后&#xff0c;具体的执行方式。 执行计划通常用于 SQL 性能分析、优化等场景。通过 EXPLAIN 的结果&#xff0c;可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引…

HBase(3):集群搭建

1 基础环境需求 jdk1.8以上Hadoopzookeeper 2 下载HBase安装包 Apache Downloads 3 安装 3.1 上传解压HBase安装包 tar -xvzf hbase-3.0.0-alpha-3-bin.tar.gz -C /opt/ 3.2 修改HBase配置文件 &#xff08;1&#xff09;修改hbase-env.sh cd /opt/hbase-3.0.0-alpha-3-bi…