了解复杂项目中的状态管理方案 Vuex了解复杂项目中的状态管理方案 Vuex了解复杂项目中的状态管理方案 Vuex
学会手写一个自己的 Vuex
了解 Vue.js 服务端渲染的使用
弄清楚为什么要使用 SSR
使用 SSR 框架 Nuxt.js 快速开发一个服务端渲染的项目

提问

  1. 一、什么是 Vuex 状态管理?

一种集中式的状态解决方案,用于处理项目多组件共享同一个状态的问题

Vuex 状态管理

课程目标

  • 组建通信方式回顾
  • Vuex 核心概念和基本使用回顾
  • 购物车案例
  • 模拟实现 Vuex

项目复杂多组件共享同一个状态的时候,组件间的通信比较麻烦,Vuex 作为集中式的状态解决方案可以处理此类问题。

组件内状态管理流程

image.png

每个组件内部都有自己的数据、模板、方法,也可以称之为状态、视图、行为。
我们所说的状态管理指的是通过状态,集中管理和分发,解决多个组件共享状态的问题
状态管理的组成:

  • state:驱动应用的数据源(状态)
  • view:通过把状态绑定到视图呈现给用户
  • actions:响应在 view 上的用户输入导致的状态变化行为,用户和视图交互改变视图的方式

组件间通信方式回顾

  • 父组件给子组件传值
  • 子组件给父组件传值
  • 不相关组件之间传值

image.png

父组件给子组件传值

  • 子组件通过 props 接收数据
  • 父组件中给子组件通过相应属性传值

props可以接收两种值,对象或数组,如果是对象,可约定传入对象类型

子组件给父组件传值

子组件通过自定义事件来实现给父组件传值
自定义事件是父组件中注册的,子组件中通过$emit触发父组件注册的自定义事件,并向其传值
在父组件使用子组件的时候使用v-on注册子组件中的自定义事件
在父组件的事件处理函数中接收自定义事件传入的值

另一种方式是在行内获取自定义事件传递数据的时候直接通过$event获取

核心在于子组件在内部触发事件的时候携带参数,然后在父组件中注册子组件内部触发的事件,并接收传递的数据,完成子向父的传值,在注册事件的时候,行内可以通过$event来获取自定义事件传递的参数

不相关组件之间传值

不相关组件之间的通信也是使用自定义事件的方式,但是与之前传值不同的是不能由子组件触发传值的方式,这里使用的是Event Bus,就是创建一个公共的vue实例,这个vue实例的作用是作为事件组件或者事件中心,
这里的不相关组件的关系包含兄弟组件的关系
eventbus.js文件只是用来导出Vue的实例,其目的是为了调用$init和$on,用来触发和注册事件
这个机制就是发布订阅模式

通过ref获取子组件

除了前三种组件通信的方式外还有其他的几种方式
$root、$parent、$children、$refs
$refs可以用来获取子组件的实例,通过实例改变子组件的状态值
image.png
如果滥用的话容易导致状态管理的混乱

问题

  • 多个视图依赖同一状态
  • 来自不同视图的行为需要变更同一状态

为了解决这些问题,我们可以把不同组件中的共享状态抽取出来,存储到一个全局对象中,并且保证将来使用的时候是响应式的,这个对象创建好后,任何组件都可以获取或者修改全局对象中的状态

简易版的状态管理我们称之为store

什么是Vuex

  • Vuex 是专门为 Vue.js 设计的状态管理库
  • Vuex 采用集中式的方式存储需要共享的状态
  • Vuex 的作用是进行状态管理,解决复杂组件通信,数据共享
  • Vuex 集成到了 devtools 中,提供了 time-teravel 时光旅行历史回滚功能

什么情况下使用 Vuex

  • 非必要情况下不要使用 Vuex
  • 大型的单页应用程序
    • 多个视图依赖于同一状态
    • 来自不同视图的行为需要变更同一状态

image.png
state是我们管理的全局状态,把状态绑定到组件也就是视图上
用户与视图交互通过Dispatch分发Actions,此处为什么不直接触发Mutations,因为Acitons中可以进行异步的操作
异步操作结束时可以通过提交Mutation记录状态的更改
Mutation必须是同步的,这样做的目的是为了通过Mutation追踪到所有状态的变化,阅读代码的时候更容易追踪状态的改变,还可以记录每一次状态的改变,实现高级的调试功能

Vuex 核心概念

  • Store:仓库,每一个应用仅有一个Store,Store是一个容器包含应用中的大部分状态,我们不能直接改变Store中的状态,我们需要通过提交Mutations的方式改变状态
  • State:状态,State存储在Store中,Store是唯一的,State也是唯一的,称为单一状态树,但是所有的状态都保存在State中的话,会让程序难以维护,可以通过后续的模块解决该问题,要注意这里的状态是响应式的
  • Getter:计算属性,方便从一个属性派生出一个其他的值,其内部可以对计算结果做响应的缓存,只有依赖的状态发生改变的时候才会进行相应的计算
  • Mutation:状态的变化需要通过提交Mutation完成
  • Action:Action和Mutation类似,但是可以进行异步的操作,内部改变状态的时候都需要提交Mutation
  • Module:模块,由于使用单一状态树,应用的所有状态都会集中到一个比较大的对象上来,当应用变得非常复杂时,Store对象就会变得非常臃肿,为了解决这个问题Vuex允许我们将Store分割成几个模块,每个模块拥有自己独立的State、Getter、Mutation、Action甚至是嵌套的子模块

Vuex 基本结构

image.png
在store模块中导入Vue和Vuex
通过Vue.use(Vuex)注册插件
插件内部将Vuex.Store注入到了Vue的实例上
创建Vuex.Store 对象并且导出
在App.js中导入Store对象
创建Vue实例的时候传入Store选项
这个Store选项会被注入到Vue实例之中

Store

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。
image.png
使用 mapState 简化 State 在视图中的使用,mapState 返回计算属性
mapState 有两种使用的方式:
接收数组参数
image.png
接收对象参数:接收对象参数可以解决命名冲突的问题
image.png

Get

Getter 就是Store中的计算属性,使用mapGetter 监视视图中的使用
image.png

Mutation

image.png
不要在mutation中执行异步操作修改state
mutation的调用commit

Action

image.png
Action的调用通过dispatchimage.png

Module

image.png

购物车组件

  • 商品列表
  • 购物车列表组件
  • 我的购物车组件

商品列表

  • 展示商品列表
  1. 在Sotre文件下创建modules模块文件夹
  2. 定义模块文件中的常亮并且导出模块对象配置命名空间namespaced:true
  3. 在Store文件夹下的index.js中也就是vuex中导入模块并在vuex的modules中配置写入的模块
  4. 回到之前定义的模块文件中,定义state商品数据,在mutations中添加方法修改state,在actions中添加异步请求商品数据的方法并调用mutations定义的方法给state添加商品数据
  5. 在页面中设置计算属性computed,开启了命名空间后第一个入参是数组要映射的入参属性
  • 添加购物车

功能列表

  • 购物车列表
  • 全选
  • 数字文本框加减功能
  • 删除
  • 统计选中商品的数量和总价

Vuex插件

image.png
image.png
创建完插件后需要在创建store实例时进行plugins注册
subscribe的作用是订阅store中的mutation,其回调函数会在每个mutation完成之后调用

模拟Vuex

创建一个Store类
基于VUE的插件规则创建一个install函数
在install中可以获取到vue的构造函数
在顶部声明一个_vue变量来存储install中获取到的vue构造函数
在install内部先将传入的vue存储到_vue中
在最外层导出Store和install
code.png
在install中,我们要将创建vue实例的时候,传入的store对象注入到vue原型上的$store
在所有组件中可以直接通过this.$store,来获取到vuex中的仓库,从而可以在所有组件中共享状态
在install中我们获取不到vue实例,所以我们通过混入beforeCreate来获取vue实例,从而拿到选项中的store对象
在beforeCreate中首先判断当前vue实例的$options中是否有store,如果是组件实例的话没有store选项,我们在其中挂载store
code.png
接下来是store类如何实现
首先需要一个构造函数接收一个对象
store中应该有与核心概念对应的几个属性,其中state是响应式的,还具备两个方法,分别是commit和dispatch用来提交mutation和分发actions
code.png

服务端渲染

同构应用

image.png

  • 什么是渲染
  • 传统的服务端渲染
  • 客户端渲染
  • 现代化的服务端渲染(同构渲染)

什么是渲染?
渲染指的是把数据和模板拼接到一起,渲染的本质是字符串的解析替换
我们要关注的不是如何渲染,而是在哪里渲染

为什么客户端首屏渲染慢?
因为需要在客户端进行三个步骤的请求,才有完整内容展示,而服务端渲染只需要一次内容直出

为什么客户端渲染不利于seo?
客户端的网页内容在被seo检索的时候是空的内容,原因是客户端需要解析js脚本来呈现完整内容,seo相当于字符串获取检索而不是浏览器回去执行脚本加载

现代化客户端渲染同构渲染

同构渲染 = 后端渲染 + 前端渲染
image.png
如何实现同构渲染
使用Vue、react等框架的官方解决方案
同构渲染的问题

  • 开发条件限制
  • 设计构建设置和部署的更多要求
  • 更多的服务器端复杂

Nuxt.js是什么

一个基于Vue.js生态的第三方开源服务端渲染应用框架
可以帮我们轻松的使用vue.js技术构建同构应用
gii

Nuxt项目的搭建

按照文档创建项目
初始化git
配置gitgone,忽略依赖包和.nuxt

  1. git add .
  2. git commit -m "xxx"
  3. git branch 分支名称 创建本地分支
  4. git checkout 分支名 切换本地分支
  5. git checkout -b 分支名 创建新本地分支的同时切换到该分支

页面之间使用路由,nuxt-link
了解路由规则默认配置结构

路由跳转
nuxt-link或者编程式导航
nuxt-link和vue的routerLink是一个东西,可以在.nuxt文件夹中查看router文件
如果通过a链接跳转会走服务端渲染导致页面刷新
编程式导航参照vueRouter中的编程式导航

动态路由得根据nuxt提供的文件常见规则定义

$route和$router的区别是,前者是当前的路由对象,后者是全局的路由对象

嵌套路由
创建同名文件夹和文件,文件夹下的文件会被视作子路由

自定义路由配置可以到api中寻找router

Nuxt.js异步数据-asyncData
用于异步数据请求放在服务端请求而不是客户端
但在非首屏情况下,客户端spa激活时作为客户端请求
但要注意的是只能在页面组件中使用
因为是在组件初始化之前调用的所以其内部无法使用this

异步数据上下文对象
为asyncData放入一个入参,该入参会被视作上下文对象,可在该对象中获取路由传参,也可以通过他获取到$route实例

Nuxts.js综合案例

项目初始化

创建项目

  1. mkdir realworld-nuxtjs
  2. cd realworld-nuxtjs
  3. npm init -y
  4. npm install nuxt
  1. // package.json 中添加启动脚本
  2. "script":{
  3. "dev":"nuxt"
  4. }

创建pages目录,配置初始页面
执行 npm run dev 项目启动

导入样式资源

根目录创建app.html,在官网获取默认模板放入
在提供的模板中提取三个样式文件引入到默认模板中
image.png
对于一些国外资源的文件加载,可以进行本土化
打开网站www.jsdelivr.com,搜索相关的国内的cdn链接,根据版本选择找到对应的链接在进行替换

布局组件

page文件件下创建layout文件夹并在内部创建index.vue
配置顶部导航栏和底部内容,中间嵌入子路由入口
image.png

设置完毕后创建home页面,在nuxt.config.js中配置自定义路由规则,通过splice清空默认生成的路由规则,设置layout作为主页面,内部嵌套home作为子路由

code.png

导入登录注册页面以及剩余页面导入

code.png

处理顶部导航链接并设置高亮

将导航栏的a标签跳转都替换为nuxt-link
在route中配置linkActiveClass自定义class,来替换默认高亮class
image.png
在路由跳转中放入exact开启精确匹配,防止嵌套路由导致的导航链接高亮

封装请求模块并给输入框添加表单验证

安装axios
项目根目录创建utils文件夹
文件夹内创建request.js文件
code.png
创建api文件夹,内部创建模块请求文件
表单验证required会视作必填项目

登录注册

实现基本登录功能

封装请求方法

表单验证

错误处理

用户注册

解析存储登录状态实现流程

与以往的登录状态存储不同,要考虑到服务端和客户端都能获取到登录状态的存储
Nuxt提供了跨域身份验证
不能存储到storage中,否则服务端无法获取
Cookie.set(‘user’,data.user)

将登录状态存储到容器中

登录状态持久化

处理导航栏链接展示状态

处理页面访问权限

通过中间件处理未登录需要授权保护的页面
在需要保护的页面中载入中间件
中间件的定义
image.png
中间件的载入
image.png

首页

  • 文章列表展示
  • 文章列表分页
  • 公共的文章列表与关注的文章列表,非登录状态关注的文章列表入口不显示
  • 标签列表作为改变文章类型,同时更改tabTitle

    展示公共文章列表

    封装对应的公共文章列表接口
    image.png
    在首页页面中将请求放入asyncData中,请求完毕将需要的结果return,目的是为了首屏渲染
    image.png
    样式单个class条件激活

    1. <button
    2. class="btn btn-outline-primary btn-sm pull-xs-right"
    3. :class="{active:article.favorited}"
    4. >
    5. <i class="ion-heart"></i> {{article.favoritesCount}}
    6. </button>

    分页参数的使用

    页码处理

    监听query参数改变使用watchQuery监听参数,当数据改变自动触发asyncData

    展示文章标签列表

    优化并行异步任务

    异步任务无依赖关系可并行处理

    处理标签列表链接和数据

    处理导航栏

    标签高亮及链接

    展示用户关注的文章列表

    统一设置用户token

    通过axios的请求拦截器,利用nuxt的插件机制注册插件,通过插件机制获取上下文,拦截器通过插件获取到的上下文取得token写入请求headers中
    code.png

    时间发布格式化处理

    使用第三方依赖来自己配置插件,在plugins目录下创建dayjs.js文件
    写入过滤规则
    code.png

    文章点赞

    文章详情

    展示基本信息

    markdown 转 HTML

    使用markdown it 将markdown转化为html

    展示文章作者相关信息

    设置页面meta优化seo

    搜索nuxt官网,视图:HTML头部
    个性化特定页面的meta标签

    通过客户端展示评论内容

    发布部署

  • 配置 Host + port

  • 压缩发布包
  • 把发布包传到服务器
  • 解压
  • 安装依赖
  • 启动服务

处理中:C端小程序_从团详情页面购买:对于落地配类型商品,页面展示错误,显示成了一件代发的页面,导致无法下单
pm2 的启动 pm2 start npm — start
终止pm2 stop id
注意:前缀命令需要软链接配置,要确保版本对应正确,并且是在node文件夹下执行
scp realworld-nuxtjs.zip root@121.4.116.89:/root/realworld-nuxtjs
npm install —global pm2

  • 发表文章评论
  • 用户的关注或取消关注
  • 文章点赞和取消
  • 评论删除
  • 删除文章
  • 编辑文章
  • 发布文章

    解答