Vue3补充

非父子组件的通信

这里我们主要讲两种方式:
Provide/Inject;
Mitt全局事件总线;
Provide/Inject用于非父子组件之间共享数据:
比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容;在这种情况下,如果我们仍然将props沿着组件链逐级传递下去,就会非常的麻烦;
对于这种情况下,我们可以使用 Provide 和 Inject :
注意要引用data中的数据会有this问题,应将provide写成函数,只适用于上下级关系,不适用兄弟关系。如果data的数据会变化写成计算属性
父组件

import { computed } from ‘vue’;先导入computed

 provide() {
      return {
        name: "why",
        age: 18,
        length: computed(() => this.names.length) // ref对象 .value
      }
    },
    data() {
      return {
        names: ["abc", "cba", "nba"]
      }
    },

孙组件
因为computed返回的是一个ref对象,需要取出其中的value来使用;
length.value取data值

<template>
  <div>
    HomeContent: {{name}} - {{age}} - {{length.value}}
  </div>
</template>

<script>
  export default {
    inject: ["name", "age", "length"],
  }
</script>

全局事件总线mitt库
首先,我们需要先安装这个库:
npm install mitt
其次,我们可以封装一个工具eventbus.js:
eventbus.js

import mitt from 'mitt';

const emitter = mitt();
// export const emitter1 = mitt();
// export const emitter2 = mitt();
// export const emitter3 = mitt();

export default emitter;

app.vue下有home.vue和about.vue,home.vue下有homecontent.vue文件
现在想点击about.vue中的按钮发出一个事件让homoecontent.vue监听到可以用mitt
about.vue

<template>
  <div>
    <button @click="btnClick">按钮点击</button>
  </div>
</template>

<script>
  import emitter from './utils/eventbus';

  export default {
    methods: {
      btnClick() {
        console.log("about按钮的点击");
        emitter.emit("why", {name: "why", age: 18});
        // emitter.emit("kobe", {name: "kobe", age: 30});
      }
    }
  }
</script>

homoecontent.vue

<template>
  <div>
  </div>
</template>

<script>
  import emitter from './utils/eventbus';

  export default {
    created() {
      emitter.on("why", (info) => {
        console.log("why:", info);
      });

      // emitter.on("kobe", (info) => {
      //   console.log("kobe:", info);
      // });

      // emitter.on("*", (type, info) => {
      //   console.log("* listener:", type, info);
      // })
    }
  }
</script>

插槽的使用
app.vue
插槽可以插入组件,也可以插入普通文本

<my-slot-cpn>
  我是普通的文本
</my-slot-cpn>

<my-slot-cpn>
  <my-button/>
</my-slot-cpn>

<my-slot-cpn></my-slot-cpn>

myslotbtn.vue
给slot中写的是默认内容,如果没有插入内容则显示

<template>
  <div>
    <slot>
      <i>我是默认的i元素</i>
    </slot>
  </div>
</template>

具名插槽
父组件

<template>
  <div>
    <nav-bar :name="name">
      <template #left>
        <button>左边的按钮</button>
      </template>
      <template #center>
        <h2>我是标题</h2>
      </template>
      <template #right>
        <i>右边的i元素</i>
      </template>
    </nav-bar>
  </div>
</template>

子组件

<template>
  <div class="nav-bar">
    <!-- <slot name="default"></slot> -->
    <div class="left">
      <slot name="left"></slot>
    </div>
    <div class="center">
      <slot name="center"></slot>
    </div>
    <div class="right">
      <slot name="right"></slot>
    </div>
  </div>
</template>

作用域插槽
让父组件写插槽时能访问到子组件的data,这样父组件可以决定子组件数据的显示方式,比如用span 用p 用button…显示
父组件
names是父组件给子组件的props

<show-names :names="names">
  <template v-slot="coderwhy">
    <button>{{coderwhy.item}}-{{coderwhy.index}}</button>
  </template>
</show-names>

子组件
通过:item=“item” :index="index"传输数据给父组件

<template>
  <div>
    <template v-for="(item, index) in names" :key="item">
      <slot :item="item" :index="index"></slot>
    </template>
  </div>
</template>

注意: 如果父组件还有其他的具名插槽, 那么默认插槽也必须使用template来编写

<show-names :names="names">
  <template v-slot="coderwhy">
    <button>{{coderwhy.item}}-{{coderwhy.index}}</button>
  </template>

  <template #why>
    <h2>我是why的插入内容</h2>
  </template>
</show-names>

动态组件
动态组件是使用 component 组件,通过一个特殊的attribute is 来实现
我们要想实现点击切换组件可以使用这个
这个currentTab的值需要是什么内容呢?
可以是通过component函数注册的组件;
在一个组件对象的components对象中注册的组件;
currentTab是data中的数据,也就是子组件名称,点击事件中改变currentTab的值即可,传值发射事件跟组件一样

  <component:is="currentTab"></component>

keep-alive使用
比如我们将counter点到10,那么在切换到home再切换回来about时,状态是否可以保持呢?
答案是否定的;
这是因为默认情况下,我们在切换组件后,about组件会被销毁掉,再次回来时会重新创建组件; 但是,在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:keep-alive。
include="home,about"表示缓存home和about页面

<keep-alive include="home,about">
  <component
    :is="currentTab"
  >
  </component>
</keep-alive>

对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的
但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;
这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听;

Webpack的代码分包和异步组件
默认的打包过程:
默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组
件模块打包到一起(比如一个app.js文件中);
这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢;
打包时,代码的分包:
所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js; 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容;
通过import函数导入文件打包出来的代码是分包的
如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent。
异步导入组件

 import { defineAsyncComponent } from 'vue';
 const AsyncCategory = defineAsyncComponent(() => import("./AsyncCategory.vue"))

到时候这个组件会被打包到单独的包里,一般是把路由写成异步组件形式

Suspense的使用
Suspense是一个内置的全局组件,该组件有两个插槽:
pdefault:如果default可以显示,那么显示default的内容;
fallback:如果default无法显示,那么会显示fallback插槽的内容;
一般就是加载时候用

<suspense>
  <template #default>
    <async-category></async-category>
  </template>
  <template #fallback>
    <loading></loading>
  </template>
</suspense>

$refs的使用
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:
在Vue开发中我们是不推荐进行DOM操作的;
这个时候,我们可以给元素或者组件绑定一个ref的attribute属性;
可以访问子组件data methods…
在这里插入图片描述

我们可以通过$parent来访问父元素

Mixin的使用
目前我们是使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取

在这里插入图片描述
如果Mixin对象中的选项和组件对象中的选项发生了冲突,那么Vue会如何操作呢?
这里分成不同的情况来进行处理;
情况一:如果是data函数的返回值对象 p返回值对象默认情况下会进行合并;
如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据;
情况二:如何生命周期钩子函数
生命周期的钩子函数会被合并到数组中,都会被调用;
情况三:值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。
比如都有methods选项,并且都定义了方法,那么它们都会生效;
但是如果对象的key相同,那么会取组件对象的键值对;

Composition API

为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方; p在Vue组件中,这个位置就是 setup 函数;
setup其实就是组件的另外一个选项:
只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项; 比如methods、computed、watch、data、生命周期setup函数的参数等等;
我们先来研究一个setup函数的参数,它主要有两个参数:
第一个参数:props
第二个参数:context
props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:
对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;
并且在template中依然是可以正常去使用props中的属性,比如message;
如果我们在setup函数中想要使用props,那么不可以通过 this 去获取,因为setup函数没绑定this
因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;
另外一个参数是context,我们也称之为是一个SetupContext,它里面包含三个属性:
attrs:所有的非prop的attribute,比如组件的class id;
slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);
emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);
一般可以通过解构使用这三个属性{ attrs, slots,emit},这样就不用通过context.的形式使用

<template>
  <div>
    <h2>{{message}}</h2>
    <h2>{{title}}</h2>
  </div>
</template>

<script>
  export default {
    props: {
      message: {
        type: String,
        required: true
      }
    },
    setup(props, {attrs, slots, emit}) {
      console.log(props.message);
      console.log(attrs.id, attrs.class);
      console.log(slots);
      console.log(emit);
      return {
        title: "Hello Home",
        counter: 100
      }
    },
  }
</script>

setup函数的返回值
setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?
psetup的返回值可以在模板template中被使用;
也就是说我们可以通过setup的返回值来替代data选项
setup是在created之前调用的,所以不能在setup里访问computed,data等的数据。不能用this访问实例是因为setup函数源码没有绑定this。

如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数
reactive是引入vue中的reactive
reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:

 <h2>当前计数: {{state.counter}}</h2>
setup() {
  const state = reactive({
    counter: 100
  })

  // 局部函数
  const increment = () => {
    state.counter++;
    console.log(state.counter);
  }

  return {
    state,
    increment
  }
}

这个时候Vue3给我们提供了另外一个API:ref API
pref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
它内部的值是在ref的 value 属性中被维护的;
注意:
在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;
但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;

<h2>当前计数: {{counter}}</h2>
<button @click="increment">+1</button>
  import { ref } from 'vue';
setup() {
  let counter = ref(100);
  const increment = () => {
    counter.value++;
    console.log(counter.value);
  }

  return {
    counter,
    increment
  }
}

ref是浅层解包,如果外面还有对象包裹,那么就要用.value

在这里插入图片描述
认识readonly

我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个
响应式对象希望在另外一个地方(组件)被使用,但是不能被修改
pVue3为我们提供了readonly的方法;
preadonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);
比如 const info = readonly(obj),info对象是不允许被修改的;

从reactive解构出来的数据不是响应式
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive
返回的state对象,数据都不再是响应式的:

Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;传入的必须是reactive对象
那么我们再次进行结构出来的 name 和 age 本身都是 ref的
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:

  setup() {
      const info = reactive({name: "why", age: 18});
      // 1.toRefs: 将reactive对象中的所有属性都转成ref, 建立链接
      let { name, age } = toRefs(info);
      // 2.toRef: 对其中一个属性进行转换ref, 建立链接
      let { name } = info;
      let age = toRef(info, "age");
      const changeAge = () => {
        age.value++;
      }
      return {
        name,
        age,
        changeAge
      }
    }

computed用法

  setup() {
      const firstName = ref("Kobe");
      const lastName = ref("Bryant");

      // 1.用法一: 传入一个getter函数
      // computed的返回值是一个ref对象
      const fullName = computed(() => firstName.value + " " + lastName.value);

      // 2.用法二: 传入一个对象, 对象包含getter/setter
      const fullName = computed({
        get: () => firstName.value + " " + lastName.value,
        set(newValue) {
          const names = newValue.split(" ");
          firstName.value = names[0];
          lastName.value = names[1];
        }
      });

      const changeName = () => {
        // firstName.value = "James"
        fullName.value = "coder why";
      }

      return {
        fullName,
        changeName
      }
    }

watch
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听;
watchEffect用于自动收集响应式数据的依赖;
watch需要手动指定侦听的数据源;
首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
watchEffect

setup() {
      // watchEffect: 自动收集响应式的依赖
      const name = ref("why");
      const age = ref(18);

      const changeName = () => name.value = "kobe"
      const changeAge = () => age.value++

      watchEffect(() => {
        console.log("name:", name.value, "age:", age.value);
      });

      return {
        name,
        age,
        changeName,
        changeAge
      }
    }

setup中使用ref
在讲解 watchEffect执行时机之前,我们先补充一个知识:在setup中如何使用ref或者元素或者组件?
其实非常简单,我们只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可;

<template>
  <div>
    <h2 ref="title">哈哈哈</h2>
  </div>
</template>

<script>
  import { ref, watchEffect } from 'vue';

  export default {
    setup() {
      const title = ref(null);

      watchEffect(() => {
        console.log(title.value);
      }, {
        flush: "post"
      })

      return {
        title
      }
    }
  }
</script>

但是这样会打印两次title.value,第一次是null,第二次是< h2 ref=“title”>哈哈哈< /h2 >,因为watchEffect会立即执行,setup调用时在dom未挂载前。
那么我们要解决这个问题需要传入第二个参数
如果我们希望在第一次的时候就打印出来对应的元素呢?
这个时候我们需要改变副作用函数的执行时机;
它的默认值是pre,它会在元素 挂载 或者 更新 之前执行;
所以我们会先打印出来一个空的,当依赖的title发生改变时,就会再次执行一次,打印出元素; 我们可以设置副作用函数的执行时机
如下设置即可

   watchEffect(() => {
        console.log(title.value);
      }, {
        flush: "post"
      })

Watch的使用

watch的API完全等同于组件watch选项的Property: pwatch需要侦听特定的数据源,并在回调函数中执行副作用;
默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调; 与watchEffect的比较,watch允许我们:
懒执行副作用(第一次不会直接执行);
更具体的说明当哪些状态发生变化时,触发侦听器的执行;
访问侦听状态变化前后的值

侦听数据源
watch侦听函数的数据源有两种写法:
一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref);
或者直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)

  const info = reactive({name: "why", age: 18});
  // 1.侦听watch时,传入一个getter函数
  watch(() => info.name, (newValue, oldValue) => {
    console.log("newValue:", newValue, "oldValue:", oldValue);
  })
  // 2.传入一个可响应式对象: reactive对象/ref对象
  //情况一: reactive对象获取到的newValue和oldValue本身都是reactive对象
  watch(info, (newValue, oldValue) => {
    console.log("newValue:", newValue, "oldValue:", oldValue);
  })

如果我们想拿到内部的值,而不是reactive对象那么

 // 如果希望newValue和oldValue是一个普通的对象
      watch(() => {
        return {...info}
      }, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })

对于reactive对象默认是深度侦听,对于解构的普通对象侦听不会触发深度侦听,如果我们希望侦听一个深层的侦听,那么依然需要设置 deep 为true:

 watch(() => ({...info}), (newInfo, oldInfo) => {
        console.log(newInfo, oldInfo);
      }, {
        deep: true,
        immediate: true
      })

生命周期
setup() 函数的执行时间 会在 beforeCreate() 和 created() 之前,所以setup替代 beforeCreate() 和 created() ,如果想要在创建组件前执行一些事那么可以写在setup函数里面。
其他生命周期可以用onxx替代写在setup里面
在这里插入图片描述
Provide函数和Inject
我们可以通过 provide来向子组件提供数据:
可以通过 provide 方法来定义每个 Property;
provide可以传入两个参数:
name:提供的属性名称;
value:提供的属性值;
在这里插入图片描述

在 后代组件 中可以通过 inject 来注入需要的属性和对应的值:
可以通过 inject 来注入需要的内容;
pinject可以传入两个参数:
要 inject 的 property 的 name;
默认值;
在这里插入图片描述
想要响应式数据就套上一个ref
如果我们需要修改可响应的数据,那么最好是在数据提供的位置来修改(单向数据流):

我们可以将修改方法进行共享,在后代组件中进行调用;
在这里插入图片描述
自定义hook
计数器
useCounter.js

import { ref, computed } from 'vue';

export default function() {
  const counter = ref(0);
  const doubleCounter = computed(() => counter.value * 2);

  const increment = () => counter.value++;
  const decrement = () => counter.value--;

  return {
    counter, 
    doubleCounter, 
    increment, 
    decrement
  }
}

app.vue

  setup() {
      const { counter, doubleCounter, increment, decrement } = useCounter();
      return {
        counter,
        doubleCounter,
        increment,
        decrement,
      }
    }

上面代码就体现了setup的好处
自定义一个localstorage的hook,功能有存 取 改变响应
useLocalStorage.js

import { ref, watch } from 'vue';

export default function(key, value) {
  const data = ref(value);
 
  if (value) {
    window.localStorage.setItem(key, JSON.stringify(value));
  } else {
    data.value = JSON.parse(window.localStorage.getItem(key));
  }

  watch(data, (newValue) => {
    window.localStorage.setItem(key, JSON.stringify(newValue));
  })

  return data;
}

// 一个参数: 取值
// const data = useLocalStorage("name");

// // 二个参数: 保存值
// const data = useLocalStorage("name", "coderwhy");

// data.value = "kobe";

app.vue

  const data = useLocalStorage("info");
  const changeData = () => data.value = "哈哈哈哈"
  

render函数的使用
h() 函数是一个用于创建 vnode 的一个函数;
其实更准备的命名是 createVNode() 函数,但是为了简便在Vue将之简化为 h() 函数;
第一个参数是元素,第二个是attarbute属性,第三个是内容
app.vue

<script>
  import { h } from 'vue';

  export default {
    render() {
      return h("h2", {class: "title"}, "Hello Render")
    }
  }
</script>

插件的编写方式
通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式:
对象类型:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行;
函数类型:一个function,这个函数会在安装插件时自动执行;
在这里插入图片描述
使用虚拟dom的原因
1.操作真实dom不方便
2.操作虚拟dom性能比真实dom高
虚拟DOM的渲染过程
首先Vue在编译时会将templete通过render函数转化成一个一个Vnode,形成一个VnodeTree,这个VnodeTree就是虚拟Dom树,然后进行真实的渲染生产真实dom树

hash和history的区别
1.哈希模式带#号,hist不带#号。
2.hash 能兼容到IE8, history 只能兼容到 IE10;所以hash的兼容性好一点。

hash模式
URL的hash也就是锚点(#), 本质上是改变window.location的href属性;
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新;
在这里插入图片描述

hsitory模式
history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:
replaceState:替换原来的路径;
pushState:使用新的路径;
popState:路径的回退;
go:向前或向后改变路径;
forward:向前改变路径;
back:向后改变路径;

router-link
to属性: 是一个字符串,或者是一个对象
replace属性: p设置 replace 属性的话,当点击时,会调用 router.replace(),而不是 router.push(); active-class属性:
设置激活a元素后应用的class,默认是router-link-active
exact-active-class属性: 链接精准激活时,应用于渲染的 < a> 的 class,默认是router-link-exact-active;
tag属性基本不用了,想要自定义的router-link可以在router-link中直接传入想要渲染的元素
在这里插入图片描述

路由懒加载
其实这里还是我们前面讲到过的webpack的分包知识,而Vue Router默认就支持动态来导入组件:
这是因为component可以传入一个组件,也可以接收一个函数,该函数 需要放回一个Promise;
而import函数就是返回一个Promise;
在这里插入图片描述
分包文件自定义名字(魔法注释)
在这里插入图片描述
如果想实现根据不同用户在url显示不同路由,那么在routes中要写成下面这样

   {
                    /* 在user后面想要动态加入id属性 */
                    path: '/user:id',
                    component: user
    }

router-link写成下面这样,那么跳转时就会携带id

   <!-- 使用v-bind进行绑定id -->
        <router-link :to="'/user'+id">用户</router-link>

在setup中调用this.$route无法获得当前路由对象,所以要用vue给我们提供的函数useRoute来使用。
在这里插入图片描述

匹配多个参数,可通过params获得
在这里插入图片描述
NotFound
对于哪些没有匹配到的路由,我们通常会匹配到固定的某个页面
比如NotFound的错误页面中,这个时候我们可编写一个动态路由用于匹配所有的页面;
在这里插入图片描述
这里还有另外一种写法:
注意:我在/:pathMatch(.*)后面又加了一个
在这里插入图片描述
区别就是加
会按数组解析参数

路由的嵌套children的path不加/
在这里插入图片描述
代码的页面跳转
有时候我们希望通过代码来完成页面的跳转,比如点击的是一个按钮:
在这里插入图片描述
如果是在setup中编写的代码,那么我们可以通过 useRouter 来获取:
在这里插入图片描述
query方式的参数
在这里插入图片描述
url
在这里插入图片描述
在界面中通过 $route.query 来获取参数
push和replace的区别
使用push的特点是压入一个新的页面,那么在用户点击返回时,上一个页面还可以回退,但是如果我们希望当前页面是一个替换操作,那么可以使用replace。

动态添加路由
比如根据用户不同的权限,注册不同的路由;
这个时候我们可以使用一个方法 addRoute;
if(管理员){
动态添加对应路由
在这里插入图片描述

}
如果我们是为route添加一个children路由,那么可以传入路由对应的name:
比如要给home路由添加子路由那么给home路由添加字段name:‘home’,然后如下就可以添加子路由
在这里插入图片描述
动态删除路由
通过removeRoute方法,传入路由的名称;
在这里插入图片描述
路由的其他方法补充:
router.hasRoute():检查路由是否存在。
router.getRoutes():获取一个包含所有路由记录的数组。

路由导航守卫
它有两个参数:
to:即将进入的路由Route对象;
from:即将离开的路由Route对象;在这里插入图片描述
它有返回值:
一:false:取消当前导航;
二: 不返回或者undefined:进行默认导航;
三:返回一个路由地址(会跳转到此路径):
可以是一个string类型的路径;
可以是一个对象,对象中包含path、query、params等信息;

可选的第三个参数:next
在Vue2中我们是通过next函数来决定如何进行跳转的;
但是在Vue3中我们是通过返回值来控制的,不再推荐使用next函数,这是因为开发中很容易调用多次next;
例子:
比如我们完成一个功能,只有登录后才能看到其他页面:
在这里插入图片描述
Vuex状态管理
创建Store
store本质上是一个容器,它包含着你的应用中大部分的状态(state);
第一:Vuex的状态存储是响应式的
当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
第二:你不能直接改变store中的状态
改变store中的状态的唯一途径就显示提交 (commit) mutation;
使用:
main.js

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

app.vue

<template>
  <home/>
  <h2>App:{{ $store.state.counter }}</h2>
  <button @click="increment">+1</button>
  <button @click="decrement">-1</button>
</template>

<script>
import Home from './pages/Home.vue'

export default {
  name: "App",
  components: {
    Home
  },
  methods: {
    increment() {
      this.$store.commit("increment")
    },
    decrement() {
      this.$store.commit("decrement")
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

store 中的index.js

import { createStore } from "vuex"
import home from './modules/home'
import user from './modules/user'

const store = createStore({
  state() {
    return {
      rootCounter: 100
    }
  },
  getters: {
    doubleRootCounter(state) {
      return state.rootCounter * 2
    }
  },
  mutations: {
    increment(state) {
      state.rootCounter++
    }
  },
  modules: {
    home,
    user
  }
});

export default store;

mapstate结合computed
counter和name和age是state中的键,mapstate返回一个对象,可以用…解构
使用时直接用{{counter}}这样即可
在这里插入图片描述

在setup中使用mapstate,因为mapstate对象解构返回的是一个个函数,所以不能直接{{counter}}表示
在这里插入图片描述
那么我们在setup要怎么使用呢?
通过computed包裹

这样使用{{sCounter}}

   const store = useStore()
   const sCounter = computed(() => store.state.counter)

或者

 setup() {
      const store = useStore()
      const storeStateFns = mapState(["counter", "name", "age", "height"])
      const storeState = {}
      Object.keys(storeStateFns).forEach(fnKey => {
      //因为没有this,所以绑定this为$store
        const fn = storeStateFns[fnKey].bind({$store: store})
        storeState[fnKey] = computed(fn)
      })

      return {
        sCounter,
        ...storeState
      }
    }

getters的基本使用
某些属性我们可能需要警告变化后来使用,这个时候可以使用getters:
类比vuex的computed
store.js

 getters: {
    doubleRootCounter(state) {
      return state.rootCounter * 2
    }
  },

使用

<h2>{{ this.$store.getters.doubleHomeCounter }}</h2>

getters第二个参数
如果当前getters需要用到另一个getters会使用

 getters: {
    doubleRootCounter(state,getters) {
      return state.rootCounter * 2+getters.current
    },
    current(state) {
      return state.rootCounter-1
    }
  },

mapGetters的辅助函数
跟mapstate使用方法一样
Mutation基本使用
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation:
一条重要的原则就是要记住 mutation 必须是同步函数因为devtool工具会记录mutation的日记;
每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;
Mutation携带数据
很多时候我们在提交mutation的时候,会携带一些数据,这个时候我们可以使用参数

mutations: {
    increment(state,payload) {
      state.rootCounter += payload;
    }
  },

home.vue
在这里插入图片描述
mapMutations辅助函数使用
同上
actions的基本使用
Action提交的是mutation,而不是直接变更状态;
Action可以包含任意异步操作;
这里有一个非常重要的参数context:
context是一个和store实例均有相同方法和属性的context对象(但不是store);
使用
app.vue

在这里插入图片描述

在这里插入图片描述
store.js
在这里插入图片描述

什么时候要用action?
当有数据需要保存在vuex,并且是从服务器请求的数据,那么请求过程不用在组件中写,直接在action里写,在组件中直接派发dispatch即可

mapactions的使用
在这里插入图片描述
Action 通常是异步的,那么在组件中如何知道 action 什么时候结束呢?
我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作;

在这里插入图片描述
module的基本使用
在这里插入图片描述
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象,是自己的state。
如果出现多个moudule都有重复名字的getters/mutilation等等,那么我们怎么执行到想执行的哪个函数呢?
module的命名空间
默认情况是合并的
默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:
这样使得多个模块能够对同一个 action 或 mutation 作出响应;
Getter 同样也默认注册在全局命名空间。
如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true 的方式使其成为带命名空间的模块:
当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
在这里插入图片描述

app.vue使用
home和user是子moudule

<template>
  <div>
    <h2>root:{{ $store.state.rootCounter }}</h2>
    <h2>home:{{ $store.state.home.homeCounter }}</h2>
    <h2>user:{{ $store.state.user.userCounter }}</h2>

    <hr>
    <h2>{{ $store.getters["home/doubleHomeCounter"] }}</h2>

    <button @click="homeIncrement">home+1</button>
    <button @click="homeIncrementAction">home+1</button>
  </div>
</template>

<script>
  export default {
    methods: {
      homeIncrement() {
        this.$store.commit("home/increment")
      },
      homeIncrementAction() {
        this.$store.dispatch("home/incrementAction")
      }
    }
  }
</script>

nexttick的使用
比如我们有下面的需求:
点击一个按钮,我们会修改在h2中显示的message;
message被修改后,获取h2的高度;

使用nexttick函数;
单纯获取高度是获取的是dom未更新的时候

 const addMessageContent = () => {
        message.value += "哈哈哈哈哈哈哈哈哈哈"
        console.log(titleRef.value.offsetHeight);
      }

现在需要将dom更新上去才打印高度

 const addMessageContent = () => {
        message.value += "哈哈哈哈哈哈哈哈哈哈"
        // 更新DOM
        nextTick(() => {
          console.log(titleRef.value.offsetHeight)
        })
      }

nextTick是微任务,原理就是事件循环。

history模式时,当请求的是wyh.org时,那么我们一般会重定向到wyh/home/xxx页面,那么这时是通过跟路由去请求数据,但是当用户在wyh.org/home进行刷新时,会通过wyh.org/home这个路由去请求数据,那么一般spa页面后端在nginx不会去配置wyh.org/home的数据,那么这时候请求会报404错误。
historyApiFallback
historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。
当请求不到数据时会返回index.html
boolean值:默认是false
如果设置为true,那么在刷新时,返回404错误时,会自动返回 index.html 的内容;
n object类型的值,可以配置rewrites属性:
p可以配置from来匹配路径,决定要跳转到哪一个页面;
vuecli是帮我们配好的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值