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

news2024/11/15 5:17:35

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 } = Vue
  
  createApp({
    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的 id
    key: 'storekey',
    // 修改为 sessionStorage,默认为 localStorage
    storage: window.sessionStorage,
    // 部分持久化状态的点符号路径数组,[]意味着没有状态被持久化(默认为undefined,持久化整个状态)
    paths: ['nested.data'],
  },
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/487930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

赛道冠军为AI狂飙:实在智能即将重归福州,亮相第六届数字中国建设峰会

2023年4月26日至30日&#xff0c;第六届数字中国建设峰会将在福建省福州市举行。本届峰会以“加快数字中国建设&#xff0c;推进中国式现代化”为主题&#xff0c;由国家网信办、国家发展改革委、科技部、工业和信息化部、国务院国资委、福建省人民政府共同主办。 作为我国信息…

代码随想录算法训练营第二十五、二十七天 | 细节很多、树枝去重和树层去重的区分是难点、分割

216.组合总和III 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算法如何剪枝&#xff1f;| LeetCode&#xff1a;216.组合总和III_哔哩哔哩_bilibili 状态&#xff1a;能做出来。和上一题没什么区别。思路直接…

SpringCloud微服务的熔断、限流、降级是怎么回事?

概述&#xff1a; 在开发公司商城项目时&#xff0c;由于采用的是微服务架构&#xff0c;每个模块之间使用OpenFeign组件进行通信&#xff0c;在遇到高并发时&#xff0c;为了保证系统的可用性和 可靠性&#xff0c;我们使用了阿里的Alibaba的Sentinel组件进行降级、限流和熔断…

vue2实现高德地图 JSAPI 2.0轨迹回放组件(MoveAnimation)

vue2实现高德地图 JSAPI 2.0轨迹回放组件(MoveAnimation) 声明: 本人是做java后端的,组件抽取不是很规范请大家见谅 前提: 需要注册高德开放平台,之后创建应用并且开通Web端(JS API)平台,然后拿到securityJsCode和key 实现效果: 1. 基础抽取 注意: 将securityJsCode和key修改为…

OpenGL(四)——纹理

目录 一、前言 二、纹理环绕方式 三、纹理过滤 3.1 邻近过滤 3.2 线性过滤 3.3 多级渐远纹理 四、加载、创建纹理 4.1 数据输入 4.2 生成并加载纹理 4.3 应用纹理坐标 4.4 顶点着色器配置纹理 4.5 片段着色器配置纹理 4.6 显示纹理 五、纹理单元 一、前言 为每个…

云原生技术概谈

云原生技术概谈 说起“云原生技术”&#xff0c;大家可能有点懵&#xff0c;只闻其声&#xff0c;不明其意。但是云原生背后典型的几个公司或者技术产品的名称可能大家经常听到&#xff1a; 比如容器技术的代表公司docker&#xff1b;容器编排技术开源产品kubernetes&#xff0…

推荐算法实战项目:DeepCross 原理以及案例实战(附完整 Python 代码)

本文要介绍的是由斯坦福大学联合Google的研究人员发表的论文《Deep & Cross Network for Ad Click Predictions》中提出的Deep&Cross模型&#xff0c;简称DCN。 DCN模型是Wide&Deep的改进版本&#xff0c;其中Deep部分的设计思路与Wide&Deep没有发生本质的变化…

计算机视觉的应用3-批量图片风格迁移之素描图片生成的应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用3-批量图片生成素描图片的应用&#xff0c;将一张图像转换为素描风格的图像的其实是模拟了人类视觉在观察物体时受到的光照条件。素描风格的图像在灰度值上表现出明暗交替的效果&#xff0c;这种效…

【干货】一文说透分布式一致性协议(下)

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"&#xff01; 作者&#xff1a;大能老师 | 慕课网讲师 前情提示&#xff1a;如需阅读“一文说透分布式一致性协议&#xff08;上&#xff09;”&#…

对多个点进行直线拟合操作

在图像处理中&#xff0c;通常会遇到根据给定的点集&#xff08;比如轮廓&#xff09;拟合出一条直线的情形。 import numpy as np import matplotlib.pyplot as plt import cv2def Cal_kb_linear_fitline(pointLocs):loc np.array(pointLocs) # loc 必须为矩阵形式&#xff…

二分类结局变量Logistic回归临床模型预测(二)——基线特征及三线表绘制(二)

本节讲的是二分类结局变量的临床模型预测,与之前讲的Cox回归不同,https://lijingxian19961016.blog.csdn.net/article/details/124088364https://lijingxian19961016.blog.csdn.net/article/details/124088364https://lijingxian19961016.blog.csdn.net/article/details/1300…

蓝牙耳机哪款性价比高一些?2023年性价比最高的蓝牙耳机推荐

随着科技的进步&#xff0c;蓝牙耳机已然成为我们生活中的一部分&#xff0c;无论是通勤、追剧、运动或者玩游戏&#xff0c;大都会用到蓝牙耳机。那么&#xff0c;哪款蓝牙耳机的性价比高一些&#xff1f;相信大多数人在选择产品的时候&#xff0c;都会看性价比。接下来&#…

手把手带你写一份优秀的开发求职简历(五)技术能力如何凸显优势

前言 前面的几小节&#xff0c;把个人信息和教育背景的模块做了讲述&#xff0c;这两个模块处于简历的第一屏最顶部&#xff0c;可以说HR会第一眼看见&#xff0c;所以很重要&#xff0c;同时也通过一些讲述告诉求职者从这些方面如何扬长避短&#xff0c;抓住HR的招聘心理。 …

Android基于JNA集成调用第三方C/C++的so库

Android基于JNA集成调用第三方C/C的so库 &#xff08;1&#xff09;引入JNA。 基于JNA开源项目&#xff0c;JNA对Android NDK的封装&#xff0c;简化Android层JNI集成调用C/C的so库。 GitHub - java-native-access/jna: Java Native AccessJava Native Access. Contribute to…

为AI造“楚门世界” 人类围观对话机器人社交、谈恋爱

不满足于跟ChatGPT聊天&#xff0c;技术狂人开始为对话机器人创造“楚门的世界”&#xff0c;从上帝视角围观AI如何“吃饭”、社交、谈恋爱...... 最近&#xff0c;一位神秘人创建了一个AI社交网站Chirper&#xff0c;人类不能参与&#xff0c;只能围观上万名AI在这里的聊天和…

社交技能讲座笔记

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 感谢张鹏老师做了一堂实用的社交技能讲座。我特地做了一些笔记&#xff08;其中包含我的一些理解&#xff09;&#xff1a; 1.整理好外观&#xff0c;让别…

【大数据之Hadoop】二十三、Yarn命令行操作及生产环境下的配置

1 Yarn常用命令 Yarn状态查询可以在hadoop103:8088页面查看&#xff0c;也可以通过命令查看。 先运行案例再查看运行情况。 &#xff08;1&#xff09;yarn application 查看任务 yarn application -list //列出所有application yarn application -list -appStates 状态 …

c++ 11标准模板(STL) std::vector (五)

定义于头文件 <vector> template< class T, class Allocator std::allocator<T> > class vector;(1)namespace pmr { template <class T> using vector std::vector<T, std::pmr::polymorphic_allocator<T>>; }(2)(C17…

Qt音视频开发43-采集屏幕桌面并推流(支持分辨率/矩形区域/帧率等设置/实时性极高)

一、前言 采集电脑屏幕桌面并推流一般是用来做共享桌面、远程协助、投屏之类的应用&#xff0c;最简单入门的做法可能会采用开个定时器或者线程抓图&#xff0c;将整个屏幕截图下来&#xff0c;然后将图片传出去&#xff0c;这种方式很简单但是性能要低不少&#xff0c;一般采…

R语言的贝叶斯时空数据模型

时间&#xff0d;空间数据&#xff08;以下简称“时空数据”&#xff09;是最重要的观测数据形式之一&#xff0c;很多科学研究的数据都以时空数据的形式得以呈现&#xff0c;而科学研究目的可以归结为挖掘时空数据中的规律。另一方面&#xff0c;贝叶斯统计学作为与传统统计学…