-
2021-05-19 17:33:23
1. 方法一 在脚手架创建项目时,选择vuex即可
2. 方法二 在项目写一半需要用vux
1. 下载安装vuex模块
cnpm i vuex --save
2. 在/src目录下新建文件夹strore/index.js 导入vuex
import Vue from 'vue' import Vuex from 'vuex' //在vue中使用vuex Vue.use(Vuex)
3. 在/src/store/index.js创建库,并导出
export default new Vuex.Store({ //state中定义状态数据,类似于全局变量,只能通过代码读,改,不能增删 state: { isLogin:false }, //mutations中存放修改状态数据的函数,一般是同步操作 mutations: { //函数的参数state指向当前对象的state,value是传入的最新值 setLogin(state,value){ state.isLogin=value } }, //actions:也是定义函数,可实现异步操作,如本地读取,如ajax等 actions: { //例如初始化函数 init(){ //在这里可以发起ajax请求,获取项目初始状态值,赋给state //比如:饿了么,qq,页面初始化获取地址定位 window.navigator.geolocation.getCurrentPosition(function(result){ console.log(result.coords) }) } }, //modules 状态管理模块化,把状态管理分成多个模块,以模块化形式导入 modules: { moduA,moduB } }) var moduA = { state:{} } var moduB = { state:{} }
4. 在main.js导入store/index.js状态管理创库,把他添加到vue跟组件中
import store from './store' new Vue({router,store})
5. 在组件中使用状态数据
在vue组件导入
import {mapState} from 'vuex'
在技算属性中使用//使用映射函数把状态数据state映射到计算属性中 computed:{ //参数是数组,存放需要使用的state字段 ...mapState(['isLogin']) }
在组件模板中使用
<!-- 在映射到技算属性中之后,当作计算属性来使用 --> 登录状态:{{isLogin}}
6. 在组件中修改状态数据
映射到技算属性中之后,当作计算属性来使用 -->
登录状态:{{isLogin}}## 6. 在组件中修改状态数据 this.@store.commit("setLogin",true)
更多相关内容 -
vue之状态管理
2021-12-13 11:28:47状态管理 一、定义 二、使用步骤与用法 一、定义 Vuex 是专门为 Vue.js 设计的状态管理库,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。简单的讲,就是全局变量,...一、定义
Vuex 是专门为 Vue.js 设计的状态管理库,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。简单的讲,就是全局变量,并且可以根据页面数据的变化而变化,也可影响页面的数据。
二、使用步骤与用法
主要组成部分是State(相当于data)、Getter(相当于computed计算属性)、Mutation(相当于methods但是不可执行异步)、Action(解决上一个不能执行的异步,可执行异步)、Module(大型项目为方便管理拆分多个子模块)
1.安装vuexnpm install vuex --save
2.新建vuex文件夹,然后在里面新建.js,之后引入vuex
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ })
之后在main.js中
import store from '.vuex/store' new Vue({ store, })
3.使用
在新建的js中Vue.use(Vuex); const state={ name:'Ashley', age:23 }; const mutations={ changename(state){ state.name='Jane' }, changeage(state,payload){ state.age+=payload } }; const actions={ changenameAsync(context){ let _name=context.state.name setTimeout(()=>{ context.commit('异步修改名字') },1000) }, changeageAsync(context,payload){ setTimeout(()=>{ context.commit('异步修改年龄',payload) },1000) } }; const getters={ name:state=>{ ...... return ...... } } //带参数的 // name:state=>val=>{ // ...... // return ...... // } export default new Vuex.Store({ state, mutations, actions, })
也可以在使用的组件中import {mapState} from 'vuex' import {mapMutations} from "vuex" export default{ computed:{ ...mapSate(['name'])//若是需要修改名字,()里面加的是对象形式,使用的this.name }, methods:{ ...mapMutations(["changeage"]), lili(10){ ......//其他 this.changename(10) ......//其他 } } }
更改状态唯一方法,规定只能通过mutation更改store状态,包括action也需要mutation去修改
-
Vue中大型项目的总结(纯原创)
2021-06-09 14:33:07Vue中后台项目的前端总结技术栈:样式:CSS 预处理器的选择:局部样式与全局样式:异步请求:--- 重点封装 Axios:跨域问题:Mock 数据:规则:路由:--- 重点布局三大类:权限验证:1.auth2.permissionsVuex:组件库:过滤...欢迎点击: 个人官网博客
创作不易,喜欢就收藏吧!
技术栈:
- 基于 @vue/cli 搭建基础模板(大家都比较了解,节省开发时间,远胜于从零开始搭建)。
- 开发一些中大型的系统,vue-router 和 vuex 都是必不可少的。
- 快速开发UI组件库 Element UI 或Ant Design Vue(相对美观一点)。
- 工具库选择 lodash。
- 根据业务需求自己定义封装各种开发中可能用到的功能,
如:
组件库
状态管理
过滤器
指令、
CSS内置变量、
表单验证、
工具函数
样式:
CSS 预处理器的选择:
- Sass/Scss
- Less
- Stylus
公司更倾向于使用 scss 开发,less 是为了覆盖 ant design vue 的样式,stylus 比较少人用。
局部样式与全局样式:
局部样式:
一般都是使用 scoped 方案:<style lang="scss" scoped> @import "base.css"; ... </style>
全局样式:
variable.scss: 全局变量管理
mixins.scss: 全局 Mixins 管理
global.scss: 全局样式其中 variable.scss 和 mixins.scss 会优先于 global.css 加载,并且可以不通过 import 的方式在项目中任何位置使用这些变量和 mixins。
// vue.config.js module.exports = { css: { loaderOptions: { sass: { prependData: ` @import '@/styles/variable.scss'; @import '@/styles/mixins.scss'; `, }, }, }, }
异步请求:
封装 Axios:
在 @/libs/request.js 路径下对 Axios 进行封装,封装了请求参数,请求头,以及错误提示信息、 request 拦截器、response 拦截器、统一的错误处理、baseURL 设置等。
import axios from 'axios'; import get from 'lodash/get'; import storage from 'store'; // 创建 axios 实例 const request = axios.create({ // API 请求的默认前缀 baseURL: process.env.VUE_APP_BASE_URL, timeout: 10000, // 请求超时时间 }); // 异常拦截处理器 const errorHandler = (error) => { const status = error.response?.status//缩写,表示如果error.response存在就拿error.response.status switch (status) { case 400: error.message = '请求错误'; break; case 401: error.message = '未授权,请登录'; break; case 403: error.message = '拒绝访问(登录过期,请重新登录)'; break; case 404: error.message = `请求地址出错,未找到资源: ${error.response.config.url}`; break; case 405: error.message = '请求方法未允许'; break; case 408: error.message = '请求超时'; break; case 500: error.message = '服务器内部错误'; break; case 501: error.message = '服务未实现'; break; case 502: error.message = '网关错误'; break; case 503: error.message = '服务不可用'; break; case 504: error.message = '网关超时'; break; case 505: error.message = 'HTTP版本不受支持'; break; default: break; } return Promise.reject(error); }; // 声明一个 Map 用于存储每个请求的标识 和 取消函数 const pending = new Map() /** * 添加请求 * @param {Object} config */ const addPending = (config) => { const url = [ config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data) ].join('&') config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => { if (!pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去 pending.set(url, cancel) } }) } /** * 移除请求 * @param {Object} config */ const removePending = (config) => { const url = [ config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data) ].join('&') console.log('url=', url) if (pending.has(url)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 const cancel = pending.get(url) cancel(url) pending.delete(url) } } /** * 清空 pending 中的请求(在路由跳转时调用) */ export const clearPending = () => { for (const [url, cancel] of pending) { cancel(url) } pending.clear() } /** * 请求拦截器 * 每次请求前,如果存在token则在请求头中携带token */ request.interceptors.request.use((config) => { removePending(config) // 在请求开始前,对之前的请求做检查取消操作 addPending(config) // 将当前请求添加到 pending 中 //Vue.ls本地储存的工具包 const token = Vue.ls.get('ACCESS_TOKEN') if (token) { config.headers['Authorization'] = 'Bearer ' + token // } return config; }, errorHandler); // 响应拦截器 request.interceptors.response.use((response) => { removePending(response.config) // 在请求结束后,移除本次请求 const dataAxios = response.data; // 这个状态码是和后端约定的 const { code } = dataAxios; // 根据 code 进行判断 if (code === undefined) { // 如果没有 code 代表这不是项目后端开发的接口 return dataAxios; } else { // 有 code 代表这是一个后端接口 可以进行进一步的判断 switch (code) { case 200: // code === 200 代表没有错误 return dataAxios.data; case 'xxx': // [ 示例 ] 其它和后台约定的 code return 'xxx'; default: return '不是正确的code'; } } }, errorHandler); export default request;
应用实例:
export const getBanner = () => { return instance({ method: 'get', url: 'public/v1/home/swiperdata', params: { firstName: 'Fred', lastName: 'Flintstone' } }); }
将clearPending()方法添加到vue路由钩子函数中
router.beforeEach(async (to, from, next) => { clearPending() next(); });
跨域问题:
跨域问题一般情况直接找后端解决了,你要是不好意思打扰他们的话,可以用 devServer 提供的 proxy 代理:
不过proxy只能在开发环境使用,线上还得后端处理设置cors(如:Nodejs:res.header(“Access-Control-Allow-Origin”,"*"了解更多,点击这里),允许我们访问。
// vue.config.js devServer: { proxy: { '/api': { target: 'http://47.100.186.132/your-path/api', ws: true, changeOrigin: true, pathRewrite: { '^/api': '' } } } }
Mock 数据:
一个很常见的情况,后端接口没出来,前端在这干瞪眼。
Mock 数据功能是基于 mock.js (opens new window)开发,通过 webpack 进行自动加载 mock 配置文件。
规则:
- 所有的 mock 配置文件均应放置在 @/mock/services 路径内。
- 在 @/mock/services 内部可以建立业务相关的文件夹分类存放配置文件。
- 所有的配置文件应按照 ***.mock.js 的命名规范创建。
- 配置文件使用 ES6 Module 导出 export default 或 export 一个数组。
入口文件:
import Mock from 'mockjs'; Mock.setup({ timeout: '500-800', }); const context = require.context('./services', true, /.mock.js$/); context.keys().forEach((key) => { Object.keys(context(key)).forEach((paramKey) => { Mock.mock(...context(key)[paramKey]); }); });
示例:
import Mock from 'mockjs'; const { Random } = Mock; export default [ RegExp('/example.*'), 'get', { 'range|50-100': 50, 'data|10': [ { // 唯一 ID id: '@guid()', // 生成一个中文名字 cname: '@cname()', // 生成一个 url url: '@url()', // 生成一个地址 county: Mock.mock('@county(true)'), // 从数组中随机选择一个值 'array|1': ['A', 'B', 'C', 'D', 'E'], // 随机生成一个时间 time: '@datetime()', // 生成一张图片 image: Random.dataImage('200x100', 'Mock Image'), }, ], }, ];
路由:
布局三大类:
- frameIn:基于 BasicLayout,通常需要登录或权限认证的路由。
- frameOut:不需要动态判断权限的路由,如登录页或通用页面。
- errorPage:例如404页面。
动态路由原理:
路由需要分成两类,静态路由和动态路由。静态路由是任何菜单权限下都能查看的界面路由;动态路由是根据菜单权限动态生成的路由集合。
大体步骤:拦截路由->后台取到路由->保存路由到localStorage或vuex(用户登录进来只会从后台取一次,其余都从本地取,所以用户,只有退出在登录路由才会更新)
核心:beforeEach、addRoutes、localStorage
import axios from 'axios' var getRouter //用来获取后台拿到的路由 router.beforeEach((to, from, next) => { if (!getRouter) {//不加这个判断,路由会陷入死循环 if (!getObjArr('router')) { axios.get('https://www.easy-mock.com/mock/5a5da330d9b48c260cb42ca8/example/antrouter').then(res => { getRouter = res.data.data.router//后台拿到路由 saveObjArr('router', getRouter) //存储路由到localStorage routerGo(to, next)//执行路由跳转方法 }) } else {//从localStorage拿到了路由 getRouter = getObjArr('router')//拿到路由 routerGo(to, next) } } else { next() } }) function routerGo(to, next) { getRouter = filterAsyncRouter(getRouter) //过滤路由 router.addRoutes(getRouter) //动态添加路由 global.antRouter = getRouter //将路由数据传递给全局变量,做侧边栏菜单渲染工作 next({ ...to, replace: true }) } function saveObjArr(name, data) { //localStorage 存储数组对象的方法 localStorage.setItem(name, JSON.stringify(data)) } function getObjArr(name) { //localStorage 获取数组对象的方法 return JSON.parse(window.localStorage.getItem(name)); }
或者vue3.0
import { createRouter, createWebHashHistory } from "vue-router"; import {clearPending} from '../axios' // 静态路由 const initRouter = [ { path: "/", name: "login", component: () => import("@/views/login.vue"), }, { path: "/home", name: "home", component: () => import("@/views/home.vue"), }, ]; // 动态路由 const asyncRouter = [ { path: "/a", name: "a", }, { path: "/b", name: "b", }, ]; const asyncRouterList = function () { return new Promise((resolve) => { setTimeout(() => { resolve(asyncRouter) }, 300) }) } const router = createRouter({ history: createWebHashHistory(), routes: initRouter, }); router.beforeEach(async (to, from, next) => { //路由跳转前,清空请求 clearPending() // 请求后台路由 const a = await asyncRouterList() //添加请求到的路由 a.forEach(i => { router.options.routes.push(i); router.addRoute(i.name, i); }) next(); }); export default router;
权限验证:
通过获取当前用户的权限去比对路由表,生成当前用户具的权限可访问的路由表,通过 router.addRoutes 动态挂载到 router 上。
- 判断页面是否需要登陆状态,需要则跳转到 /user/login
- 本地存储中不存在 token 则跳转到 /user/login
- 如果存在 token,用户信息不存在,自动调用 vuex ‘/system/user/getInfo’
在路由中,集成了权限验证的功能,需要为页面增加权限时,在 meta 下添加相应的 key:
1.auth
(类型:Boolean):当 auth 为 true 时,此页面需要进行登陆权限验证,只针对 frameIn 路由有效。
2.permissions
(类型:Object):permissions 每一个 key 对应权限功能的验证,当 key 的值为 true 时,代表具有权限,若 key 为 false,配合 v-permission 指令,可以隐藏相应的 DOM。
import router from '@/router'; import store from '@/store'; import Vuefrom 'Vue'; import util from '@/libs/utils'; // 进度条 import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; const loginRoutePath = '/user/login'; const defaultRoutePath = '/home'; /** * 路由拦截 * 权限验证 */ router.beforeEach(async (to, from, next) => { // 进度条 NProgress.start(); // 验证当前路由所有的匹配中是否需要有登录验证的 if (to.matched.some((r) => r.meta.auth)) { // 是否存有token作为验证是否登录的条件 const token = Vue.ls.get('ACCESS_TOKEN'); if (token && token !== 'undefined') { // 是否处于登录页面 if (to.path === loginRoutePath) { next({ path: defaultRoutePath }); // 查询是否储存用户信息 } else if (Object.keys(store.state.system.user.info).length === 0) { store.dispatch('system/user/getInfo').then(() => { next(); }); } else { next(); } } else { // 没有登录的时候跳转到登录界面 // 携带上登陆成功之后需要跳转的页面完整路径,方便登录后直接回到该页面 next({ name: 'Login', query: { redirect: to.fullPath, }, }); NProgress.done(); } } else { // 不需要身份校验 直接通过 next(); } }); router.afterEach((to) => { // 进度条 NProgress.done(); util.title(to.meta.title); });
Vuex:
内置一些功能,主要是对以下这些功能做了一些封装:
- 用户信息管理(储存信息、对 token 进行操作等)
- 登陆(调接口)
- 菜单管理(储存路由信息,生成菜单,模糊查询等功能)
- 日志管理(消息提醒、日志留存、日志上报)
- 全屏操作
组件库:
可手动封装复用组件
过滤器:
过滤器是 Vue 提供的一个很好用的功能,vue3 已去除。
- 格式化时间
- 格式化数字金额
- 格式化文本
- 区分环境的链接(主要针对本地静态资源服务器和 OSS )
指令:
- 组件权限验证
- 文本复制
- 图片懒加载
- 焦点,文本框聚焦
- 滚动至指定位置
- 快捷键绑定
项目优化上线:
1.体验优化:
页面顶部加载条:
使用 nprogress 对路由跳转时做一个伪进度条,这样做在网络不好的情况下可以让用户知道页面已经在加载了:
npm install nprogress npm install @types/nprogress//如果使用ts
import NProgress from 'nprogress'; router.beforeEach(() => { NProgress.start(); }); router.afterEach(() => { NProgress.done(); });
美化滚动条样式:
Windows 上的滚动条十分丑陋,为了保持一致:
::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { width: 6px; background: rgba(#101F1C, 0.1); -webkit-border-radius: 2em; -moz-border-radius: 2em; border-radius: 2em; } ::-webkit-scrollbar-thumb { background-color: rgba(#101F1C, 0.5); background-clip: padding-box; min-height: 28px; -webkit-border-radius: 2em; -moz-border-radius: 2em; border-radius: 2em; } ::-webkit-scrollbar-thumb:hover { background-color: rgba(#101F1C, 1); }
静态资源加载页面(首次加载白屏):
首次加载页面时,会产生大量的白屏时间,这时做一个 loading 效果看起来会很友好,其实很简单,直接在 public/index.html 里写一些静态的样式即可。
移动端 100vh 问题:
在移动端使用 100vh 时,发现在 Chrome、Safari 浏览器中,因为浏览器栏和一些导航栏、链接栏导致不一样的呈现:
你以为的 100vh === 视口高度
实际上 100vh === 视口高度 + 浏览器工具栏(地址栏等等)的高度解决方案:
安装 vh-check npm install vh-check --save
import vhCheck from 'vh-check'; vhCheck('browser-address-bar');
定义一个 CSS Mixin
@mixin vh($height: 100vh) { height: $height; height: calc(#{$height} - var(--browser-address-bar, 0px)); }
2.构建优化:
1.包分析工具:webpack-bundle-analyzer
(构建代码之后,到底是什么占用了这么多空间)
2.开启 Gzip(后端配置支持你的 .gz 文件)
const CompressionWebpackPlugin = require('compression-webpack-plugin') const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i; // 开启gzip压缩 new CompressionWebpackPlugin({ algorithm: 'gzip', test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), threshold: 10240, minRatio: 0.8 }),
Nginx服务器也要有相应的配置:
server{ listen 8087; server_name localhost; gzip on; gzip_min_length 1k; gzip_comp_level 9; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_vary on; gzip_disable "MSIE [1-6]\."; }
3.路由懒加载
{ path: 'home', name: 'Home', component: () => import( /* webpackChunkName: "home" */ '@/views/home/index.vue' ), },
4.抽取公共包,引入CDN
//vue.config.js const externals = { vue: 'Vue', 'vue-router': 'VueRouter', vuex: 'Vuex', axios: 'axios' } const cdnMap = { css: [], js: [ '//unpkg.com/vue@2.6.10/dist/vue.min.js', '//unpkg.com/vue-router@3.0.6/dist/vue-router.min.js', '//unpkg.com/vuex@3.1.1/dist/vuex.min.js', '//unpkg.com/axios@0.19.0/dist/axios.min.js' ] } module.exports = { chainWebpack: config => { config.externals(externals) config.plugin('html').tap(args => { args[0].cdn = cdnMap args[0].minify && (args[0].minify.minifyCSS = true) // 压缩html中的css return args }) } }
然后在public/index.html添加
<!-- 使用CDN的CSS文件 --> <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" /> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> <% } %> <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %>
5.压缩图片使用image-webpack-loader
6.去除console
npm i -D babel-plugin-transform-remove-console
在 babel.config.js 中配置
// NODE_ENV const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV); const plugins = [ //按需引入elementui [ "component", { libraryName: "element-ui", styleLibraryName: "theme-chalk", }, ], ]; // 去除 console.log if (IS_PROD) { plugins.push("transform-remove-console"); } module.exports = { presets: ["@vue/cli-plugin-babel/preset", ["@babel/preset-env", { modules: false }]], plugins: plugins, };
其他一些优化:
1.合理使用keep-alive避免不必要的加载
2.合理使用本地存储一些简单的数据
3.图片懒加载
npm install vue-lazyload --save-dev
<img v-lazy="imgsrc"> <img v-lazy:background-image="imgsrc">
4.使用字体图标代替图片,或使用精灵图(雪碧图),或svg图片
-
Vue3 - 新的状态管理方式
2021-02-05 00:20:52简单来说,Vuex 是 Vue 的状态管理插件。提供了开箱即用的响应式功能,并且它很好的集成到了 Vue 开发者工具中。在广泛使用 Vuex 后,我不得不说它真的不讨我喜欢。因为在模块代码中会包含一些不必要...Vue3 中是否仍然需要 Vue Store?对我来说答案是 否定 的!原因如下所示。
为何不应该使用 Vuex
如果还不了解 Vuex ,可以在此找到更多相关信息:vuex.vuejs.org/。
简单来说,Vuex 是 Vue 的状态管理插件。提供了开箱即用的响应式功能,并且它很好的集成到了 Vue 开发者工具中。
在广泛使用 Vuex 后,我不得不说它真的不讨我喜欢。因为在模块代码中会包含一些不必要的模板和结构。
大多数人想通过 Vuex 实现什么?
我认为最常见的用法是在组件间共享状态。此外,Vuex 还会确保应用的状态只能在程序的特定位置修改。
我认为,上述情况其实无需使用 Vuex。你可以简单的引入自己的存储机制而无需受到 Vuex 复杂性的限制和影响。
样板
看一下代码以便理解我的意思:
let state = {
count: 0
}
let actions = {
incrementCount({commit}) {
commit('incrementCount')
}
}
let mutations = {
incrementCount(state) {
state.count++
}
}
export default {
namespaced: true,
state,
mutations
}
现在看下我们实际需要的:
export const clickStore = {
state: {
count: 0
},
incrementCount() {
clickStore.state.count++;
}
}
随着存储状态的增长,问题只会越变越大,因为你总是需要去应对状态的变化和其行为。我的建议是,仅使用 actions 去使状态发生改变。其他所有的逻辑都应该拆分为独立的服务文件。
由于 Vuex 建议仅在 mutation 块儿中改变状态,因此你基本都会写成如上所述的样板代码。不过值得高兴的是,我们接下来将实现一种替代方案。啪的一下,很快啊 :)
代码结构
不幸的是,你依然需要在状态存储内处理一系列的字符串。并且如果你有后端经验或者管理一些主要的代码库,你就会知道重构一个依赖于字符串的调用是一个多么痛苦的过程。
因此,提交一个行为或者更改存储总会触发带有字符串关键字的函数,就如下面这样:
incrementCount({commit, dispatch}) {
commit('incrementCount')
dispatch('someOtherAction')
}
相信你已经看到了重构这样的代码会有多少的麻烦,并且你可能永远不会更改这些变更操作和行为的名称,因为这意味着你的应用可能会被玩坏。
Vuex 的 Map 函数部分的解决了这个问题,详情请见:vuex.vuejs.org/guide/actions.html#...。不过重构依然是个问题。
不过你可能会问了,JS 在重构上不总会有各种问题么?
说的没错。这也是我推荐你使用 TypeScript 的原因之一。我曾试着仅使用 JS 构建一个企业级的软件,然后不出所料的失败了。我将会把详细情况整理为博客并发布到此主题下,并粘贴链接至此。
不过 Vuex 没有官方的 typescript 的实现,我们又迷茫了。我目前已知的唯一一个相关项目是 github.com/paleo/direct-vuex (一个超赞的开源项目!)并且作者在项目中也非常活跃,不过我个人是不会将其用作企业环境的。
因此,让我们一起看下如何使用 TypeScript 实现状态存储,并且无需那些无用的模板代码 :)
Vue 3 救急
所以就像我们指出来的那样,你在 Vue3 中再也无需使用 Vuex 状态存储了。实现自己的状态存储非常的简单,并且比 Vuex 的更加灵活且更加可维护。需要注意的是,下面的示例将使用 TypeScript 编码。如果你想看看与之匹配的普通 js 文件,请访问:github.com/Mario-Brendel/Vue3-Test...
首先,确定下我们的目标,我不想要一个全局状态(虽然这更容易实现) - 而且,我还打算在应用的不同部分对应有不同的状态存储。理想情况下,应用中的所有的模块都应该是独立的(状态存储也应如此)。
存储
我想先封装一下 Vue 响应式的逻辑。并且让开发人员可以不用考虑这部分的实现方式。接下来看下如何实现:
import {reactive, readonly} from 'vue';
export abstract class Store {
protected state: T;
constructor() {
let data = this.data();
this.setup(data);
this.state = reactive(data) as T;
}
protected abstract data(): T
protected setup(data: T): void {}
public getState(): T {
return readonly(this.state) as T
}
}
首先声明了一个继承自 Object 类的 Storage 类。这样做可以保证只有对象会被用于这个状态。下面你也会领略到我这样做的用意。
接下来,看下初始化了响应式数据的构造函数(这就是我想封装的那部分代码)。
看看这行代码:
this.state = reactive(data) as T;
这就是魔法!!!Vue3 的响应方法让我们的对象(来自抽象的 data() 函数) 具备响应式能力。除此之外,我们还添加了 as T 这一部分。这可以确保没有人可以知道我们处理的是代理对象并且在里边完整的执行了正确的代码。:)
另外,我们的抽象类有着这三个方法
protected abstract data(): T
protected setup(data: T): void {}
public getState(): T{
return readonly(this.state) as T
}
现在,你可以忽略前两个方法,不过 getStage() 方法非常重要。这里我们将会用到源自 Vue3 的 readonly 函数。这样即可确保状态只能在该类内部被更改(还适用于数组和嵌套对象)。因此,其他所有的实例只能获取到我们对象的只读版本。如果你试着修改状态存储中的值,比如下面这个操作:
store.getState().test = “123”
你将会在开发控制台中收到警告,并且这个操作会被忽略。
这就是我喜欢用 Vue3 的原因!!!使用 Vue2 你将无法使用这么强的功能,因为所有的响应式操作都与 Vue 实例紧密相连。并且,你看下上面的代码,不存在一个 Vue 实例 - 你可以直接获得响应式的能力。
存储实现
让我们开始实际的实现状态存储并且在某个地方用一用。为此,我创建了一个叫做 click-store.ts 的文件,这个文件可以获知给定按钮的点击次数(可以认为是给这个小博客中的功能做一个状态存储 vue-3-alpha-has-started )。
import {Store} from "./main";
interface Click extends Object {
count: number
}
class ClickStore extends Store {
protected data(): Click {
return {
count: 0
};
}
incrementCount() {
this.state.count++;
}
}
export const clickStore: ClickStore = new ClickStore()
首先,我们提供了一个描述状态并将其用作存储类型的参数。如此一来,data() 函数就必须返回一个 Click 对象。在 data() 方法中,我们需要初始化必要的状态,然后 Store 父类将会使其支持响应式。
接下来,我们可以创建一个 incrementCount() 方法,并且不需要使用函数注入之类的手段 - 就像 Vuex 中的 commit 一样 - 调用一个其他函数从而改变状态。
。。。就是如此神奇。每个新的状态存储都只需挂在 data() 函数并且提供一些方法来修改状态即可。让我们看下在 Vue 文件中会是什么样的体现。
在 Vue 文件中使用状态存储
Clicked {{ countState.count }} times.
import {clickStore} from "../store/click-store";
export default {
setup() {
const inc = () => {
clickStore.incrementCount()
// should throw a warning and don't mutate the store
clickStore.getState().count++
}
return {
countState: clickStore.getState(),
inc
}
}
}
如你所见,我创建了一个 inc() 函数,它首先调用了 incrementCount() 方法,这个方法由状态存储提供,然后尝试直接改变这个状态。第二个调用将不会有任何信息发生改变,因为我们使用 readonly 函数来阻止这一操作。
最后,我们通过返回值提供状态。请一定要记住,你需要在模板中提供状态。否则你将失去变量响应式的能力。因为这是状态对象的一部分,而不是一个独立的对象。比如你像下边这样写:
count: clickStore.getState().count,
那么存储数据的变更将不会在模板中注册。
总结
只需要短短几行代码,即可提供一个不错的状态存储解决方案,并且比 Vuex 存储更具灵活性也更易维护。此外,通过与 TypeScript 的结合,或得了类型检查和开箱即用的能力,让你的编码更加轻松 :)。
虽然看起来我不喜欢 Vuex,不过我依然认为有适合使用它的场景。。。不过对于大多数项目来说,我觉着不使用 Vuex 更好。
关于这个话题如果你有任何的疑惑或者反馈,请留言或邮件 :)。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
-
vue状态管理、Vuex使用详解
2019-03-02 14:09:47state :全局访问的state对象,存放要设置的初始状态名及值(必须要有) mutations :里面可以存放改变 state 的初始值的方法 ( 同步操作--必须要有 ) getters :实时监听state值的变化可对... -
Vuex-全局状态集中式管理神器,做vue项目不知道Vuex真的out了
2021-06-21 10:25:04官方解释:Vuex是一个专为Vue.js应用程序开发的状态管理模式。 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex也集成到Vue的官方调试工具... -
搞不定大型项目?4个项目实践看清Vue.js的真正实力
2020-09-14 17:06:02全文共3777字,预计学习时长10分钟 图源:unsplash ...作为基于JavaScript的框架,Vue.js最近几年中发展迅速,原因很多,包括框架的简单...因此,本文将讨论4种可用于组织大型Vue.js项目的最佳实践。 1... -
如何组织大型 vue 应用
2020-12-30 05:44:54本文介绍如何使用 Vue CLI 命令行,结合业务逻辑和国际化功能,更好组织项目结构。简介vue.js 作为一款优秀框架,学习曲线很小,很容易配置,并且可以很...下面是我在编写大型 vue.js 应用时用的应用组织结构。项目... -
路由vue-router、状态管理Vuex
2020-11-14 18:28:07使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 vue-router 添加进来,我们需要做的是,将组件(components)映射到路由(routes),然后告诉 vue-router 在哪里渲染它们。 快速入门 初始化工程 # ... -
使用vue之vuex进行状态管理
2019-03-05 09:55:37这种设计让状态管理变得非常简单而直观 Vue实现这种数据双向绑定的效果,需要三大模块: Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 Compile:对每个元素节点的指令进行扫描和... -
在react中使用vue的状态管理的方法示例
2020-11-21 02:50:35我是要介绍一个新的 react 全局共享状态管理器,它和 vue 组件的状态管理一起同工之妙。 马上体验 在 react 状态管理领域,react-redux 可谓是只手遮天了,基于 flux 思想实现,小巧,immutable 的思想让数据变化可... -
全新的 Vue3 状态管理工具:Pinia
2021-12-18 01:01:59Vue 的周边生态都在加紧适配这套新的系统,官方的状态管理库 Vuex 也在适配中,为此官方提出了一个 Vuex 5 的全新提案。 支持两种语法创建 Store:Options Api 和 Composition Api; 删除 mutations,只支持 state、... -
Vue状态模式实现窗口停靠功能(灵动、自由, 管理后台Admin界面)
2020-11-21 09:06:26昨天做的tabs窗口,非常满意,今天乘胜追击,把它做成了可以根据自身大小改变显示样式,自身宽度过小时,tab页可以浮动停靠其一侧。具体效果: 左侧 右侧 向来喜欢简单明了的东西,所以想实现的简单一点,无奈... -
vue项目优化
2021-09-12 10:51:06vue项目的优化分为三部分: Vue 代码层面的优化 webpack 配置层面的优化 Web 技术层面的优化 一、代码层面的优化 1 v-for 遍历为 item 添加 key 在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,... -
vue项目--资产管理系统
2019-05-17 18:18:40项目名称 XX资产管理系统 前端技术栈 ...vue/node.js/ES6 网络请求原理 ...对vue的要求略高,其他技术会用就行 ...vue项目运行依赖 axios 基于promise用于浏览器和node.js的http客户端 https://www.k... -
【VUE】demo01-VUE做后台管理系统页面实例-创建基本环境+页面布局
2022-02-02 21:32:27前提:记录一下使用vue cil2脚手架做后台管理页面,是仿照学习macrozheng 的 mall 项目的后台管理前端系统学习的,因为对vue只了解一点点,熟练都谈不上,但是要做项目就需要把前端也实现一下,所以学习一下大概的... -
xsm:状态管理使Angular,React和Vue非常简单有效
2021-02-01 06:39:50XSM-状态管理使Angular,React,Vue和Svelte非常简单有效。 :house: 演示版 强调 易于使用,友好的开发人员和最小的学习曲线 React性强,无干扰 自动重新渲染和状态数据删除,高效的内存管理 超级简单的异步处理 ... -
基于Vue的多项目整合实践
2020-12-20 10:35:18这会出现一种情形,产品在不断迭代过程中,由于之前线上前端代码并非工程化项目,后面新需求多是另起Vue项目来进行编码上线,前端在整个业务线上没有统一的工程,项目过多分布散乱并且不易优化管理。(项目指根据新... -
vue3 关于使用vuex做状态管理,页面刷新后数据丢失解决办法
2021-05-21 17:36:31原因是页面刷新后浏览器会重新加载页面相关的组件,vuex的存储的状态当然也会被初始化,所以就造成了数据丢失。也就是vuex并不能做数据的持久化存储,只是在内存中创建个临时仓库。 解决方式 使用其他存储方式 ... -
Pinia下一代vue状态管理工具
2022-01-21 13:25:56Pinia是一个围绕Vue 3 Composition API的封装器,Pinia 是一个用于 Vue 的状态管理库,类似 Vuex, 是 Vue 的另一种状态管理方案,Pinia 支持 Vue2 和 Vue3。官网地址: Pinia 2、pinia的优点? 符合直觉,易于... -
vue3.0新型状态管理和逻辑复用方式
2020-08-05 08:00:00前言背景:2019年2月6号,React 发布「16.8.0」版本,vue紧随其后,发布了「vue3.0 RFC」Vue3.0受React16.0 推出的hook抄袭启发(咳咳...... -
中大型项目用react还是vue?
2021-07-01 18:21:41中大型项目用react还是vue?中大型项目可以用react,react比较适合大型项目。原因:1、大型项目的庞大带来的是代码优化以及性能优化react提倡的更细粒度的封装,带来的组件的复用性提高。更高自由度的编写(几乎无api... -
Vue生态全新状态管理工具Pinia学习记录
2022-02-14 17:00:58Vue生态全新状态管理工具Pinia学习 -
Vue2项目构建 后台管理经典模板
2018-09-18 16:04:10Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。 此项目搭建用了Vue2 + iview, iview是很不错的一套UI框架,他像bootstrap一样可以适配各种分辨率。 一.初始化简单demo 1.vue2也有... -
为什么有些vue项目推荐使用Vuex进行管理?VueX的基本使用方法
2021-06-10 00:26:57Vue是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据的共享。 -
前端实战项目:前端框架Vue3.0项目教程(一)Vue3.0环境的搭建
2021-08-25 15:38:29在很多公司的前端框架使用中,Vue做为一门技术流行开来,具备了虚拟DOM树,面向组件的开发模式和观察者模式等优点。 现在,Vue也进入了3.0的时代。 一、Vue3.0的特点 vue最主要的特点就是响应式机制、模板... -
vue完整项目开发流程
2021-01-19 14:08:57vue完整项目开发流程创建项目,初始化项目配置多环境变量配置sass封装axios封装vuex按需引入我们需要的框架路由封装封装5个常用的公共组件使用节流防抖,函数防抖优化项目项目优化,项目打包上线 创建项目,初始化... -
【项目实践】使用Vue.js和ElementUI快速实现后台管理系统的界面布局
2019-11-01 09:02:22前言 大家都知道,后台管理界面不需要很酷炫的动画效果,也不需要花里胡哨的界面展示,只需要简洁易用、便于管理数据。...在这里我会用Vue.js和ElementUI来快速搭建起这样的后台管理界面布局! 准备... -
浅谈在vue-cli3项目中解决动态引入图片img404的问题
2020-11-19 20:38:09博主最近手头再做一个项目,需要调用天气接口,并且还要动态显示天气相关图片icon。 本来以为没什么大问题,结果硬生生被这个动态图片路径搞得民不聊生(博主还是 too young,too simple~),给出效果图: 就是那个... -
Vue 自定义项目的创建以及配置
2022-04-14 19:53:14使用vue create 创建自定义项目以及配置