v-if 和 v-for的优先级问题
vue2 肯定是v-for他的优先级高, 在同一个元素中使用,会发生需要每循环一次就判断一次。 将数据计算出来再循环。 用计算属性来代替 v-for + if
<div v-for="item of [1,2,3,4,5]">
<template >
<div v-if="item%2 === 0">div>
<template>
div>
在vue3 中v-for的优先级低于v-if。 v-if 会被抽离到v-for的上一层元素中,如果有依赖关系,建议提升到计算属性后使用 查看模板编译的结果
vue2
{{ item }}
function render() {
with(this) {
return _c('div', _l(([1, 2, 3, 4, 5]), function (item) {
return (item % 2 === 0) ? _c('div', [_c('div', [_v(_s(item))])]) :
_e()
}), 0)
}
}
vue3
{{ item }}
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, createCommentVNode as _createCommentVNode } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
(_ctx.item % 2 === 0)
? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, _renderList([1, 2, 3, 4, 5], (item) => {
return _createElementVNode("div", null, [
_createElementVNode("div", null, _toDisplayString(item), 1 /* TEXT */)
])
}), 64 /* STABLE_FRAGMENT */))
: _createCommentVNode("v-if", true)
]))
}
Vue 中的 v-show 和 v-if 怎么理解?
代码:
v-show
{{ item }}
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives, createCommentVNode as _createCommentVNode } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_ctx.item % 2 === 0)
? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, _renderList([1, 2, 3, 4, 5], (item) => {
return _createElementVNode("div", null, [
_withDirectives(_createElementVNode("template", null, [
_createElementVNode("div", null, _toDisplayString(item), 1 /* TEXT */)
], 512 /* NEED_PATCH */), [
[_vShow, false]
])
])
}), 64 /* STABLE_FRAGMENT */))
: _createCommentVNode("v-if", true)
}
// Check the console for the AST
v-if
{{ item }}
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode } from "vue"
const _hoisted_1 = { key: 0 }
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_ctx.item % 2 === 0)
? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, _renderList([1, 2, 3, 4, 5], (item) => {
return _createElementVNode("div", null, [
false
? (_openBlock(), _createElementBlock("div", _hoisted_1, _toDisplayString(item), 1 /* TEXT */))
: _createCommentVNode("v-if", true)
])
}), 64 /* STABLE_FRAGMENT */))
: _createCommentVNode("v-if", true)
}
// Check the console for the AST
const vShow = {
beforeMount(el, { value }, { transition }) {
el._vod = el.style.display === "none" ? "" : el.style.display;
if (transition && value) {
transition.beforeEnter(el);
} else {
setDisplay(el, value);
}
},
mounted(el, { value }, { transition }) {
if (transition && value) {
transition.enter(el);
}
},
updated(el, { value, oldValue }, { transition }) {
if (!value === !oldValue)
return;
if (transition) {
if (value) {
transition.beforeEnter(el);
setDisplay(el, true);
transition.enter(el);
} else {
transition.leave(el, () => {
setDisplay(el, false);
});
}
} else {
setDisplay(el, value);
}
},
beforeUnmount(el, { value }) {
setDisplay(el, value);
}
};
function setDisplay(el, value) {
el.style.display = value ? el._vod : "none";
}
Vue 3 中如何进行组件通信?
Vue 3 中如何进⾏组件通信?
父组件传递给子组件:
src/App.vue
src/components/com.vue
组件
子组件传递给父组件:
src/App.vue
src/components/com.vue
组件
组件
vue3 模版优化有哪些?
vue3中模版编译时会新增block节点 (收集动态节点的, 按照数组的维度来更新 靶向更新)
<template v-if="false">
<div>[1, 2, 3, 4, 5]div>
template>
<div>1div>
<div>1div>
<div>1div>
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, Fragment: _Fragment } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
false
? (_openBlock(), _createElementBlock("div", { key: 0 }, "[1, 2, 3, 4, 5]"))//新增block节点,收集动态节点的
: _createCommentVNode("v-if", true),
_createTextVNode(),
_createElementVNode("div", null, "1"),
_createTextVNode(),
_createElementVNode("div", null, "1"),
_createTextVNode(),
_createElementVNode("div", null, "1")
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
vue3模版编译中会对动态节点做标识 patchFlags 标记哪些属性是动态的,更新时只更新动态属性
<template v-if="false">
<div :style="{coloe:isTrue?'red':'blue'}" class="fang">[1, 2, 3, 4, 5]div>
template>
const { normalizeStyle: _normalizeStyle, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return false
? (_openBlock(), _createElementBlock("div", {
key: 0,
style: _normalizeStyle({coloe:_ctx.isTrue?'red':'blue'}),
class: "fang"
}, "[1, 2, 3, 4, 5]", 4 /* STYLE */))//patchFlags 标记哪些属性是动态的,更新时只更新动态属性;
: _createCommentVNode("v-if", true)
}
// Check the console for the AST
vue3编译的时候会对函数进行缓存优化,不用每次都创建
<div v-for="item in arr">
<div @click="handleClick">88div>
div>
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.arr, (item) => {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", {
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))//对函数进行缓存优化
}, "88")
]))
}), 256 /* UNKEYED_FRAGMENT */))
}
vue3编译的时候会将静态节点做静态提升,后续直接采用提升后的结果
<div v-for="item in arr">
<div>88div>
div>
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "88", -1 /* HOISTED */)//将静态节点做静态提升
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.arr, (item) => {
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}), 256 /* UNKEYED_FRAGMENT */))
}
// Check the console for the AST
vue3中会对同一个静态节点超过20个 会转化成字符串来渲染
<div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>12div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
<div>88div>
div>
const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("88888888888888888812888888888888", 16)//会转化成字符串来渲染
const _hoisted_17 = [
_hoisted_1
]
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, _hoisted_17))
}
// Check the console for the AST
vue2中的响应式就是Object.defineProperty。vue3就是Proxy。
说说你对双向绑定的理解,以及它的实现原理吗?
组件上的 v-model 默认会利⽤名为 modelValue 的 prop 和名为 onUpdate:modelValue 的事件。对于组件⽽⾔ v-model就是个语法糖。可⽤于组件中数据的双向绑定。
绑定的名字也可以修改:
<my v-model:a="a" v-model:b="b" v-model:c="c"> my>
vue中的双向数据绑定就是对表单元素做了一些常见的数据绑定及事件绑定。具体来说主要就是v-model了。
v-model="state"
类似于:valule="state" @input="e=>state=e.target.value"
;
代码示例:
src/App.vue
(val = e.target.value)" />
{{ val }}
src/components/com.vue
组件{{ modelValue }}
<input type="text" v-model="val"/>
const { vModelText: _vModelText, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return _withDirectives((_openBlock(), _createElementBlock("input", {
type: "text",
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((_ctx.val) = $event))
}, null, 512 /* NEED_PATCH */)), [
[_vModelText, _ctx.val]
])
}
// Check the console for the AST
const vModelText = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode);
const castToNumber = number || vnode.props && vnode.props.type === "number";
addEventListener(el, lazy ? "change" : "input", (e) => {
if (e.target.composing)
return;
let domValue = el.value;
if (trim) {
domValue = domValue.trim();
}
if (castToNumber) {
domValue = looseToNumber(domValue);
}
el._assign(domValue);
});
if (trim) {
addEventListener(el, "change", () => {
el.value = el.value.trim();
});
}
if (!lazy) {
addEventListener(el, "compositionstart", onCompositionStart);
addEventListener(el, "compositionend", onCompositionEnd);
addEventListener(el, "change", onCompositionEnd);
}
},
// set value on mounted so it's after min/max for type="range"
mounted(el, { value }) {
el.value = value == null ? "" : value;
},
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode);
if (el.composing)
return;
if (document.activeElement === el && el.type !== "range") {
if (lazy) {
return;
}
if (trim && el.value.trim() === value) {
return;
}
if ((number || el.type === "number") && looseToNumber(el.value) === value) {
return;
}
}
const newValue = value == null ? "" : value;
if (el.value !== newValue) {
el.value = newValue;
}
}
};
const vModelCheckbox = {
// #4096 array checkboxes need to be deep traversed
deep: true,
created(el, _, vnode) {
el._assign = getModelAssigner(vnode);
addEventListener(el, "change", () => {
const modelValue = el._modelValue;
const elementValue = getValue(el);
const checked = el.checked;
const assign = el._assign;
if (isArray(modelValue)) {
const index = looseIndexOf(modelValue, elementValue);
const found = index !== -1;
if (checked && !found) {
assign(modelValue.concat(elementValue));
} else if (!checked && found) {
const filtered = [...modelValue];
filtered.splice(index, 1);
assign(filtered);
}
} else if (isSet(modelValue)) {
const cloned = new Set(modelValue);
if (checked) {
cloned.add(elementValue);
} else {
cloned.delete(elementValue);
}
assign(cloned);
} else {
assign(getCheckboxValue(el, checked));
}
});
},
// set initial checked on mount to wait for true-value/false-value
mounted: setChecked,
beforeUpdate(el, binding, vnode) {
el._assign = getModelAssigner(vnode);
setChecked(el, binding, vnode);
}
};
function setChecked(el, { value, oldValue }, vnode) {
el._modelValue = value;
if (isArray(value)) {
el.checked = looseIndexOf(value, vnode.props.value) > -1;
} else if (isSet(value)) {
el.checked = value.has(vnode.props.value);
} else if (value !== oldValue) {
el.checked = looseEqual(value, getCheckboxValue(el, true));
}
}
const vModelRadio = {
created(el, { value }, vnode) {
el.checked = looseEqual(value, vnode.props.value);
el._assign = getModelAssigner(vnode);
addEventListener(el, "change", () => {
el._assign(getValue(el));
});
},
beforeUpdate(el, { value, oldValue }, vnode) {
el._assign = getModelAssigner(vnode);
if (value !== oldValue) {
el.checked = looseEqual(value, vnode.props.value);
}
}
};
const vModelSelect = {
//
deep: true,
created(el, { value, modifiers: { number } }, vnode) {
const isSetModel = isSet(value);
addEventListener(el, "change", () => {
const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map(
(o) => number ? looseToNumber(getValue(o)) : getValue(o)
);
el._assign(
el.multiple ? isSetModel ? new Set(selectedVal) : selectedVal : selectedVal[0]
);
});
el._assign = getModelAssigner(vnode);
},
// set value in mounted & updated because
//
mounted(el, { value }) {
setSelected(el, value);
},
beforeUpdate(el, _binding, vnode) {
el._assign = getModelAssigner(vnode);
},
updated(el, { value }) {
setSelected(el, value);
}
};
function setSelected(el, value) {
const isMultiple = el.multiple;
if (isMultiple && !isArray(value) && !isSet(value)) {
warn(
`${Object.prototype.toString.call(value).slice(8, -1)}.`
);
return;
}
for (let i = 0, l = el.options.length; i < l; i++) {
const option = el.options[i];
const optionValue = getValue(option);
if (isMultiple) {
if (isArray(value)) {
option.selected = looseIndexOf(value, optionValue) > -1;
} else {
option.selected = value.has(optionValue);
}
} else {
if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i)
el.selectedIndex = i;
return;
}
}
}
if (!isMultiple && el.selectedIndex !== -1) {
el.selectedIndex = -1;
}
}
function getValue(el) {
return "_value" in el ? el._value : el.value;
}
function getCheckboxValue(el, checked) {
const key = checked ? "_trueValue" : "_falseValue";
return key in el ? el[key] : checked;
}
const vModelDynamic = {
created(el, binding, vnode) {
callModelHook(el, binding, vnode, null, "created");
},
mounted(el, binding, vnode) {
callModelHook(el, binding, vnode, null, "mounted");
},
beforeUpdate(el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, "beforeUpdate");
},
updated(el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, "updated");
}
};
function resolveDynamicModel(tagName, type) {
switch (tagName) {
case "SELECT":
return vModelSelect;
case "TEXTAREA":
return vModelText;
default:
switch (type) {
case "checkbox":
return vModelCheckbox;
case "radio":
return vModelRadio;
default:
return vModelText;
}
}
}
function callModelHook(el, binding, vnode, prevVNode, hook) {
const modelToUse = resolveDynamicModel(
el.tagName,
vnode.props && vnode.props.type
);
const fn = modelToUse[hook];
fn && fn(el, binding, vnode, prevVNode);
}
v-model:xxx=""
取代了:xxx.sync=""
;v-model:xxx=""
对应的是onUpdate:xxx
事件。如何能实现 pc + 移动端一套代码在一起?
pc与移动端一套代码,两套样式。
响应式数据
的对象类型的属性值
在取值时也会变成响应式对象
,取值时会有get方法。
targetData - {{ targetData }}
theRefData - {{ theRefData }}
theRefData[0] - {{ theRefData[0] }}
handleRef
toRawData - {{ toRawData }}
targetData - {{ targetData }}
toRawData - {{ toRawData }}
markRawData - {{ markRawData }}
markRawRef - {{ markRawRef }}
handleMarkRawRef
vue3 中v-if 和 v-else 会认为是两个完全不同的元素;
vue3 切换后,text输入框与password输入框的value不共用。
<div>
<input type="text" v-if="flag">
<input type="password" v-else>
<button @click="flag=!flag">button>
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createTextVNode: _createTextVNode } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
(_ctx.flag)
? (_openBlock(), _createElementBlock("input", {
key: 0,
type: "text"
}))
: (_openBlock(), _createElementBlock("input", {
key: 1,
type: "password"
})),
_createTextVNode(),
_createElementVNode("button", {
onClick: $event => (_ctx.flag=!_ctx.flag)
}, null, 8 /* PROPS */, ["onClick"])
]))
}
// Check the console for the AST
vue2 切换后,text输入框与password输入框的value是共用的。
<div>
<input type="text" v-if="flag">
<input type="password" v-else>
<button @click="flag=!flag">button>
function render() {
with(this) {
return _c('div', [(flag) ? _c('input', {
attrs: {
"type": "text"
}
}) : _c('input', {
attrs: {
"type": "password"
}
}), _c('button', {
on: {
"click": function ($event) {
flag = !flag
}
}
})])
}
}
添加
删除
修改
确认
取消
添加
删除
修改
确认
取消
src/App.vue
src/components/Toast/index.js
import ToastComponet from "./toast.vue"
import { render, h } from "vue"
export default function toast(options) {
let message
let duration
if (typeof options === "object") {
message = options.message
duration = options.duration || 3000
} else {
message = options
duration = 3000
}
// Vue.extend(toast).$mount()
// 渲染器来渲染组件 render(toast,'...')
const container = document.createElement("div")
render(
h(
ToastComponet,
{
message,
duration,
onDestroy() {
render(null, container)
}
},
{
default: () => {
return h("h1", "hello")
}
}
),
container
) //这个函数的意思是在容器container中渲染一个组件,组件会有一个实例对象,实例对象中的虚拟节点对应的DOM元素会被插入到容器container中。而下一次如果再调用该render函数,则也是传入一个组件或null,下一个组件或null则会对应一个新的实例对象-同时该新实例对象会对应一个新虚拟节点,该新虚拟节点对应的DOM元素则会被插入到容器container中。在新容器container中新实例对象对应的新虚拟节点对应的DOM元素被插入到容器container时,旧实例对象被销毁,同时旧实例对象对应的旧虚拟节点对应的DOM也将被销毁并移除出DPM文档树中。
document.body.appendChild(container.firstElementChild)
}
// vue-router vuex
src/components/Toast/toast.vue
{{ message }}
{{ message }}
看下午的视频,约15分钟-弹框组件,看关于render(null, container)的内容。
function h(type, propsOrChildren, children) {
const l = arguments.length;
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren]);
}
return createVNode(type, propsOrChildren);
} else {
return createVNode(type, null, propsOrChildren);
}
} else {
if (l > 3) {
children = Array.prototype.slice.call(arguments, 2);
} else if (l === 3 && isVNode(children)) {
children = [children];
}
return createVNode(type, propsOrChildren, children);
}
}
const container = document.createElement("div")//创建容器对象。
render(h(组件配置,),container) //在容器container中渲染一个组件,组件会有一个实例对象,实例对象中的虚拟节点对应的DOM元素会被插入到容器container中。
document.body.appendChild(container.firstElementChild)//把容器中的第一个子元素放到body中,但容器上会存在一个虚拟节点,该虚拟节点会对应该DOM元素,如果该虚拟节点被销毁,则该DOM也将被销毁。
render(null, container)//再调用该render函数,则也是传入一个组件或null,下一个组件或null则会对应一个新的实例对象-同时该新实例对象会对应一个新虚拟节点,该新虚拟节点对应的DOM元素则会被插入到容器container中。在新容器container中新实例对象对应的新虚拟节点对应的DOM元素被插入到容器container时,旧实例对象被销毁,同时旧实例对象对应的旧虚拟节点对应的DOM也将被销毁并移除出DPM文档树中。
组件
是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
切换的动态组件CSS过渡示例:
<button @click="show = !show">Togglebutton>
<Transition>
<p v-if="show">hellop>
Transition>
/* 下面我们会解释这些 class 是做什么的 */
//进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
.v-enter-from{
opacity: 0;
}
//进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
.v-enter-active{
transition: opacity 0.5s ease;
}
//进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
.v-enter-to{}
//离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
.v-leave-from{}
//离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
.v-leave-active {
transition: opacity 0.5s ease;
}
//离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。
.v-leave-to {
opacity: 0;
}
-api面试题
后端在没有对应的资源时,直接返回首页,在后端返回首页之后,浏览器端的首页在渲染时,vue会根据当前的路径,再把首页渲染出来。
路由的导航守卫流程
导航守卫执⾏流程
src/router/index.js
import { createWebHistory, createMemoryHistory } from "vue-router"
import { createRouter, createWebHashHistory } from "vue-router"
import { h, Text } from "vue"
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/",
component: () => import("../views/bar.vue")
// meta 路由备注
// beforeEnter 路由钩子
// name 路由名
// children 路由儿子
// components :{}
},
{
path: "/foo",
component: () => import("../views/foo.vue")
// meta 路由备注
// beforeEnter 路由钩子
// name 路由名
// children 路由儿子
// components :{}
},
{
path: "/:pathMatch(.*)*",
component: {
render: () => h(Text, "not found")
}
}
]
})
export default router
src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
核心:
import router from './router'
app.use(router)
src/App.vue
前往bar页面
前往foo页面
src/views/bar.vue
bar
src/views/foo.vue
foo
选项式
import { createStore, createLogger } from "vuex"
const store = createStore({})
组合式
import { useStore } from "vuex"
const store = useStore()
vuex缺陷?
commit和dispatch 到底用哪一个? (mutation 和action区别) 修改状态交给mutation来操作,action 异步请求的封装 (可以复用异步的逻辑),有一个操作我们没有复用逻辑是不是就不需要action了? (必须经过action -》 mutation)里去。 pinia不在有mutation,只用action
树结构, 结构层次深.状态调用会变得冗余. 模块和状态命名冲突问题, 模块之间为了进行分割,而且每个模块要增加 namespaced:true属性
vuex提供了很多options的写法 createNamespaceHelpers(), mapState,mapAction mapMutations
store只能有一个, 而且都是optionsAPI
vuex不支持ts 4.x 只是将代码增加了类型,提示非常不友好, pinia 是未来的vuex
const store = new Vuex.Store({
state: {
a: 1
},
modules: {
a: {
namespaced: true,
modules: {
a: {
namespaced: true
}
}
}
}
})
store.state.a
pinia 的好处
安装下载pinia插件
npm install pinia
三步曲
src/main.js 入口文件:
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia(); // createRouter
const app = createApp(App)
// pinia.use(function ({ store }) {
// store.$state = JSON.parse(localStorage.getItem(store.$id) || '{}')
// store.$subscribe(function ({storeId},state) { // 状态修改就触发
// localStorage.setItem(storeId, JSON.stringify(state))
// })
// })
// 以前的Vue写法 都改成app.xxx
app.use(pinia); // 使用pinia
app.mount('#app')
// createPinia , defineStore
// sessionStorage 和 localStrage的区别? 有没有场景 只能放到sessionStorage
// 如果用local存储会有个问题,就是一直存储就是存储的最后一个,之前的一刷新就好变成最后一个
// 针对当前的会话场景 只能用session
// 周五 周六讲vue3项目 pinia + router + 高端写法 都用上
核心代码:
import { createPinia } from 'pinia'
const pinia = createPinia(); // createRouter
app.use(pinia);
创建一个仓库
src/stores/counter.js
// import { defineStore } from "pinia" // 定义一个store
// 为什么data是一个函数,为了保证每个组件的数据是唯一的
// vue2的vuex原理是什么? new Vue()
// vue3里的pinia如何实现 reactive() + computed()
/* import { ref, computed } from "vue"
export const useCounter = defineStore("main", () => {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increment = () => {
console.log(`选项式-setup函数`)
count.value++
}
const decrement = () => {
count.value--
}
return {
count,
double,
increment,
decrement
}
}) */
import { defineStore } from "pinia" // 定义一个store
export const useCounter = defineStore("main", {
// vuex
state: () => {
return {
count: 0
}
},
getters: {
double() {
return this.count * 2
}
},
actions: {
increment() {
console.log(`选项式-类vuex`)
this.count++
},
decrement() {
this.count--
}
}
})
// function setup() {
// const count = ref(0);
// const double = computed(() => {
// return count.value * 2
// })
// const increment = () => {
// count.value++
// }
// const decrement = () => {
// count.value--
// }
// return {
// count,
// double,
// increment,
// decrement
// }
// }
核心代码:
import { defineStore } from "pinia" // 定义一个store
export const useCounter = defineStore("main", {
// vuex
state: () => {
return {
count: 0
}
},
getters: {
double() {
return this.count * 2
}
},
actions: {
increment() {
console.log(`选项式-类vuex`)
this.count++
},
decrement() {
this.count--
}
}
})
组件中调用:
src/App.vue
counterStore.count - {{ counterStore.count }}
counterStore.double - {{ counterStore.double }}
import { defineStore } from "pinia" // 定义一个store
export const useCounter = defineStore("main", {
// vuex
state: () => {
return {
count: 0
}
},
getters: {
double() {
return this.count * 2
}
},
actions: {
increment() {
console.log(`pinia的选项式-类vuex`)
this.count++
},
decrement() {
this.count--
}
}
})
import { defineStore } from "pinia" // 定义一个store
import { ref, computed } from "vue"
export const useCounter = defineStore("main", () => {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increment = () => {
console.log(`pinia的组合式-setup函数`)
count.value++
}
const decrement = () => {
count.value--
}
return {
count,
double,
increment,
decrement
}
})
可以自己实现,也可以用官方的插件。
自己实现的思路是,每次数据变动,初始化时,调用pinia.use(),会从本地存储中取值并赋值给对应的pinia仓库。而之后可通过$subscribe()在pinia的值变动时,把最新的值存储到本地存储中。
import { createApp } from "vue"
import App from "./App.vue"
import { createPinia } from "pinia"
const pinia = createPinia() // createRouter
const app = createApp(App)
pinia.use(function ({ store }) {
console.log(`初始化-要从本地存储中取到值并填充到pinia仓库中` );
store.$state = JSON.parse(localStorage.getItem(store.$id) || "{}")
store.$subscribe(function ({ storeId }, state) {
console.log(`pinia仓库中的数据更新,取一遍pinia仓库中的值保存到本地存储中`)
// 状态修改就触发
localStorage.setItem(storeId, JSON.stringify(state))
})
})
app.use(pinia) // 使用pinia
app.mount("#app")
核心代码:
pinia.use(function ({ store }) {
store.$state = JSON.parse(localStorage.getItem(store.$id) || "{}")
store.$subscribe(function ({ storeId }, state) {
// 状态修改就触发
localStorage.setItem(storeId, JSON.stringify(state))
})
})
示例代码:
src/main.js
import { createApp } from "vue"
import App from "./App.vue"
import { createPinia } from "pinia"
const pinia = createPinia() // createRouter
const app = createApp(App)
pinia.use(function ({ store }) {
store.$state = JSON.parse(localStorage.getItem(store.$id) || "{}")
store.$subscribe(function ({ storeId }, state) {
// 状态修改就触发
localStorage.setItem(storeId, JSON.stringify(state))
})
})
app.use(pinia) // 使用pinia
app.mount("#app")
// createPinia , defineStore
// sessionStorage 和 localStrage的区别? 有没有场景 只能放到sessionStorage
// 如果用local存储会有个问题,就是一直存储就是存储的最后一个,之前的一刷新就好变成最后一个
// 针对当前的会话场景 只能用session
// 周五 周六讲vue3项目 pinia + router + 高端写法 都用上
核心代码:
pinia.use(function ({ store }) {
store.$state = JSON.parse(localStorage.getItem(store.$id) || "{}")
store.$subscribe(function ({ storeId }, state) {
// 状态修改就触发
localStorage.setItem(storeId, JSON.stringify(state))
})
})
src/stores/counter.js
import { defineStore } from "pinia" // 定义一个store
import { ref, computed } from "vue"
export const useCounter = defineStore("main", () => {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increment = () => {
console.log(`pinia的组合式-setup函数`)
count.value++
}
const decrement = () => {
count.value--
}
return {
count,
double,
increment,
decrement
}
})
src/App.vue
counterStore.count - {{ counterStore.count }}
counterStore.double - {{ counterStore.double }}
具体步骤
用你喜欢的包管理器安装依赖:
npm i pinia-plugin-persistedstate
将插件添加到 pinia 实例上
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
创建 Store 时,将 persist 选项设置为 true。
使用选项式 Store 语法:
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => {
return {
someState: '你好 pinia',
}
},
persist: true,
})
核心:
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: ...,
persist: true,//核心;
})
或者使用组合式 Store 语法:
import { defineStore } from 'pinia'
export const useStore = defineStore(
'main',
() => {
const someState = ref('你好 pinia')
return { someState }
},
{
persist: true,
}
)
核心:
import { defineStore } from 'pinia'
export const useStore = defineStore('main',() => { ... },{
persist: true,//核心属性;
})