• 初入Vue(一)


    前言

    本片来源于需求,在拿到需求到交付给需求方到交付给用户的过程中,经历了一系列的困难,也解决了一系列的困难,也收获满满。

    上手一个Vue项目想了解的很多,但是少不了如下几点:

    开发工具VSCode以及常用的插件

    创建新项目

    首先全局安装脚手架:

    npm install -g @vue/cli

    然后搭建一个空项目

    vue create report

    然后选择一系列的配置最终生成了一个新项目

    执行如下命令,即可运行:

    npm run serve

    这样就可以看到这个Vue-cli给我想看到的一个初始页面了,里面的功能很多,具体的可以仔细看下。下面我们来看下这个项目的目录结构:

    目录结构

    report

    ├─ .git git管理工具

    ├─ .gitignore git忽略文件

    ├─ GitHooks.sh 针对提交代码的时候做一些限制,比如代码中含有console.log禁止提交

    ├─ README.md 本项目一些简要重点内容说明,一般都是空白,靠开发人员填写

    ├─ babel.config.js 默认的babel配置文件

    ├─ jsconfig.json 默认文件,里面是一些项目的依赖配置

    ├─ package-lock.json 三方依赖文件的安装版本

    ├─ package.json 项目配置文件,还有一些脚本

    ├─ public 项目Html文件入口,还可以存放不参与编译的静态资源

    │ ├─ favicon.ico

    │ └─ index.html

    ├─ src 开发过程中最常使用到文件夹

    │ ├─ App.vue 项目的根组件

    │ ├─ assets 图片库

    │ │ └─ logo.png

    │ ├─ components 自定义组件库

    │ │ └─ HelloWorld.vue

    │ ├─ main.js js入口文件

    │ ├─ router 路由管理

    │ │ └─ index.js

    │ ├─ store 状态管理

    │ │ └─ index.js

    │ └─ views 页面

    │ ├─ AboutView.vue

    │ └─ HomeView.vue

    └─ vue.config.js 项目配置,再运行项目的时候会优先加载这个文件中的配置,比如配置代

    理,路径别名等等

    组件

    这个项目中的组件大致可分为3类:

    1.第三方UI组件库

    element(Vue),Ant Design (React),图表库 echarts ,手机UI组件 vant等等

    2.自定义组件

    这里是指在开发过程中封装的一些 基础组件和业务组件,存在与 src/components/ 文件夹下

    路由页面

    这里就不得不介绍 vue-router 了,因为我们是单页面应用,所有的页面跳转都需要通过vue-router来劫持路由。如果我们是多页面的话,就不需要这个了,直接通过打开新的地址就可以跳转了。

    单页面应用

    优势:跳转仅刷新局部资源 ,公共资源,例如 js、css 等仅需加载一次,页面片段间的切换快,用户体验良好,有转场动画

    缺点:需要单独方案、实现较为困难、不利于SEO检索

    多页面应用

    优势:适用于追求高度支持搜索引擎的应用

    缺点:刷新所有资源,每个公共资源,例如 js、css 等需选择性重新加载,页面切换加载缓慢,流畅度不够,用户体验比较差,转场动画难以实现

    搜索引擎优化(SEO):是指用于通过增加其搜索引擎页面排名来增加网站流量的方法。搜索引擎根据网页中的内容来给他增加权重,多页面应用内容都在HTML文档中,恰好搜索引擎只能读取到HTML中的部分,所以SEO效果好。

    所以我们大多数的项目使用的都是单页面应用

    1.页面跳转

    我们使用官方推荐的路由组件 vue-router ,这组件有如下几种跳转方式:

    1. 声明式导航
    2. router-link
    1. // 不带参数,name , path 都行, 习惯用 name 。
    2. // 如果是 '/' 开始就是从根路由开始,如果开始不带 '/' ,则从当前路由开始
    3. link :to="{name:'home'}">
    4. link :to="{path:'/home'}">
    5. // 带参数
    6. // params:使用 name 的时候用,传递参数方式类似于 post 请求
    7. // query:使用 path 的时候用,传递参数方式类似于 get 请求
    8. link :to="{name:'home', params: {id:1}}">
    9. link :to="{path:'home', query: {id:1}}">
    10. link :to="/home/:id">
    11. // 传递对象,那你非要用query,转一下对象到字符串再传递
    12. link :to="{name:'detail', query: {item:JSON.stringify(obj)}}">link>

     1.编程式导航

    1. router.push({name:'home',query: {id:'1'}})
    2. router.push({path:'/home',query: {id:'1'}})
    3. router.push({name:'home',params: {id:'1'}})
    4. router.push({path:'/home/123'})
    5. router.replace({name:'home',query: {id:'1'}})
    6. router.go(1)

    router.push()

    push的意思是跳转一个新页面,点击后退会返回到上一个页面

    router.replace()

    replace的意思是替换当前页面,点击返回会跳转到上上个页面

    router.go(1)

    go的意思是向前或者向后跳转n个页面,n可为正整数或负整数

    2.正向传值

    正向传值分为2个部分:

    页面 A 向一个新开的 B 页面传值

    A 在通过 push ,replace 等跳转的时候,通过 params 或者 query 携带参数即可向新开的 B 页面传值。

      1. 父组件向子组件传值

    如下:dataConfigs 即为子组件的传值接收者

     
    

    3.反向传值

    反向传值也分为2个部分:

      1. 新开的 B 页面向上一个页面 A 传值

    一般我们不会直接进行传值,而是通过三方组件 vuex 进行数据状态共享,对数据进行修改

      1. 子组件向父组件传值,可以向父组件抛出事件

    举个例子:

    1. // 父
    2. <script>
    3. setup() {
    4. return {};
    5. }
    6. script>
    7. // 子
    8. <template>
    9. template>
    10. <script>
    11. props: {
    12. dataConfigs: {
    13. type: Object,
    14. default: {},
    15. },
    16. },
    17. setup() {
    18. return {};
    19. }
    20. script>
    21. // 父
    22. <template>
    23. <Tab @onClickTab="onClickTab">Tab>
    24. template>
    25. <script>
    26. export default {
    27. setup() {
    28. const onClickTab = (data) => {
    29. defaultCurrent.value = data.name;
    30. };
    31. return {};
    32. }
    33. }
    34. script>
    35. // 子
    36. <template>
    37. <van-tabs
    38. ...
    39. @click-tab="(data) => onClickTab(data)"
    40. >
    41. <van-tab v-for="(item, index) in configs" :title="item.title" :key="index">
    42. van-tab>
    43. van-tabs>
    44. template>
    45. <script>
    46. setup(props, { emit }) {
    47. const onClickTab = (data) => {
    48. emit("onClickTab", data);
    49. };
    50. return {};
    51. }
    52. script>
     
    

    通过代码逻辑,我们可以清晰的看到,使用 emit 函数,即可吧方法外抛到父组件中进行使用。

    说到这个 emit ,就不得不说一下这个 setup 函数,这个是 Vue3 语法才有的,这个 setup 函数有两个参数,第一个是 props . 第二个是 context 公开四个组件属性 attrs ,slots ,expose 和 emit。

      1. props

    这个 props 是当前页面已经确定的数据,可以直接获取,并且是响应式的,当传入新的 prop 时,值将会自动改变

      1. context
        1. attrs :有状态的对象。父组件在给子组件传值的时候,如果子组件没有在 props 中声明,那么子组件要获取这个值就要在这里获取了
        2. slots:有状态的对象。插槽,顾名思义,插槽就是支持添加外部UI布局
        3. emit:在子组件内修改props的值,父组件监听变化
        4. expose:setup函数再返回一个渲染函数的时候,无法在返回对象,通过此函数可以暴露内部函数供父组件通过 ref 访问

    注:有状态的对象,它们总是会随组件本身的更新而更新,应避免进行解构,一旦解构数据就无法产生动态变化,解构就是如下写法:

    const { name } = context.attrs

    如果避免不了要使用,那就使用 toRefs 来完成,更加深入了解这个函数,可以去仔细研究一下,这里就不展开了

    4.Vuex

    为什么 Vuex 会在这里,因为他是一个页面状态管理工具。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

    一张图表示:

    说白了就是通过初始化数据源来给 view 一个状态,然后用户点击 view 上某个按钮触发 state 的改变,然后state 把改变的数据重新映射到视图上,给用户视觉反馈。

    但是当我们发生了一以下业务的时候,就会带来很多问题:

    1. 多个视图依赖于同一状态。
    2. 来自不同视图的行为需要变更同一状态。

    对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。

    对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

    这个时候我们肯定会想到如何把共用的状态抽出来,形成一个全局单例模式管理,这样就可以大大提高代码的健壮性,使代码结构化且易维护。

    这个 Vuex 在这种模式下,为组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。

    下面我们来简单的聊一下和这个使用方法,详细的文档可以去这里查阅

    在项目中有个 store 文件夹,这是 Vuex 的核心,index.js的代码如下:

    1. import { createStore } from 'vuex'
    2. export default createStore({
    3. //组件获取状态数据
    4. state: {
    5. return {
    6. count: 0
    7. }
    8. },
    9. //对state的数据做进一步的加工处理,一般用不到
    10. getters: {
    11. },
    12. //组件操作状态数据
    13. mutations: {
    14. increment (state) {
    15. state.count++
    16. }
    17. },
    18. //可以包含任意异步操作,比如处理一下不重要的页面数据,防止阻塞用户的操作
    19. actions: {
    20. },
    21. //顾名思义,分模块
    22. modules: {
    23. }
    24. })

    写好上述以后,现在,你可以通过 store.state 来获取状态对象,并通过 store.commit 方法触发状态变更:

    // 按钮方法点击调用
    store.commit('increment')

    接口请求

    1.网络请求库
    基本上到这里开发一个不带网络请求的 Vue 已经完成了,下面我们再看看一下如何去请求网络,让我们的应用跳动起来。

    Vue 常用的网络请求库是 axios

    首先在项目中导入 axios

    npm install axios

    然后拿出我们祖传的已经封装好的网络请求库。一般来说,除非公司是刚建立研发部,或者是不同一个部门开发的不同项目。公司的网络请求库都是统一的,一般不会做更改,这里我们就看一下我们封装的网络请求代码组件。

    1. import { baseUrl, defaultError } from "@/utils/config";
    2. import axios from "axios";
    3. import { Toast } from "vant";
    4. import router from "@/router";
    5. import { branch } from "./config";
    6. //基础配置,包含我们熟知的域名,headers 中我们熟知的 branch ,
    7. const baseConfig = {
    8. baseURL: baseUrl,
    9. withCredentials: true,//跨域请求携带用户凭证
    10. responseType: "json",//相应类型
    11. headers: {
    12. "Content-Type": "application/json",//x-www-form-urlencoded 和form-data 类型
    13. // 方便真机测试,不需要自行删除
    14. branch,
    15. },
    16. };
    17. //统一处理报错信息
    18. const codePond = {}; // 同一个code同一时间只会展示一次
    19. function showError(msg, code) {
    20. if (!codePond[code]) {
    21. codePond[code] = true;
    22. Toast({
    23. message: msg ? msg.replace(/\n/g, "
      "
      ) : defaultError,
    24. onClose() {
    25. codePond[code] = false;
    26. },
    27. });
    28. }
    29. }
    30. //发起网络请求
    31. const instanceQuiet = axios.create(
    32. Object.assign({}, baseConfig, {
    33. transformRequest: [
    34. function (data) {
    35. // 测试
    36. return JSON.stringify(data);
    37. },
    38. ],
    39. transformResponse: [
    40. function (data) {
    41. try {
    42. // 数据处理应放在拦截器中,一旦此处报错拦截器无法拿到状态码做出相应的错误提示,后期优化
    43. data = typeof data === "string" ? JSON.parse(data) : data;
    44. return data;
    45. } catch (error) {
    46. console.warn("error----", error);
    47. }
    48. },
    49. ],
    50. })
    51. );
    52. //相应网络请求
    53. instanceQuiet.interceptors.response.use(
    54. function (response) {
    55. return response;
    56. },
    57. function (error) {
    58. showError(defaultError);
    59. return Promise.reject(error);
    60. }
    61. );
    62. export default instanceQuiet;

    看完这里,我们再去看一下get,post的设置方式。

    1. const getBasicApis = (instanceQuiet) => {
    2. return {
    3. // 评估结果
    4. getResultById: (params) =>
    5. instanceQuiet.get(
    6. "/saas-risk-expert/v1/riskexpert/creditevaluation/getResultById",
    7. { params }
    8. ),
    9. // 查询评估结果模块整体列表信息
    10. getResultByAttr: (data) =>
    11. instanceQuiet.post("/saas-risk-expert/result/getResultByAttr", data),
    12. };
    13. };
    14. export default getBasicApis;

    如果我们不指定这个是 get 还是其他方式,默认是 get请求。里面有很多可配置的参数,比如超时,服务器响应的数据类型,上传下载处理进度事件,响应内容的最大尺寸等等,这里就不展开讲了。

    2.环境切换

    使用了子环境以后,我们常用的向 h5 页面我们可以通过浏览器的 ModHeader 插件来很好的支持这个子环境切换。但是移动端体现的就不是很友好。移动端需要在代码内手动修改这个子环境名称,每次修改需要重新打包,需要耗费很多的时间。除此外,还需要对移动端内部的 h5 资源页面特殊处理。需要通过抓包的形式把 h5 页面链接拿出来,放到浏览器中使用。引发这些问题的原因主要是访问 h5 资源也需要指定子环境,不然就访问master 环境的仓库了。目前的话我们有2中解决方案:

    1. 把测试代码直接合到 master ,这种只适合新项目
    2. 通过抓包软件,在抓包软件中设置 header 的子环境,也可以达到切换子环境的效果。

    上面说的是子环境的切换,那么我们开发中主环境切换是怎么做的呢,我们在 vue.config.js 文件中配置代理,在切换环境的时候在 utils/config.js 文件中进行切换即可

    常见问题思考

    1. 不同页面的相同样式名称的布局会被覆盖?

    在 style 中设置 scoped ,就会让它的css样式只能用于当前的 Vue 组件,其中的 scss 是指支持 scss 写法,

    scss 写法

    1. <style lang="scss" scoped>
    2. .empty1 {
    3. margin-top: 30px;
    4. margin-left: 30px;
    5. margin-right: 30px;
    6. background: #ffffff;
    7. box-shadow: 8px 16px 15px 0px rgba(35, 62, 183, 0.12);
    8. border-radius: 16px;
    9. border: 1px solid rgba(216, 221, 226, 0);
    10. .empty2 {
    11. margin-top: 30px;
    12. margin-left: 30px;
    13. margin-right: 30px;
    14. background: #ffffff;
    15. box-shadow: 8px 16px 15px 0px rgba(35, 62, 183, 0.12);
    16. border-radius: 16px;
    17. border: 1px solid rgba(216, 221, 226, 0);
    18. }
    19. }
    20. style>

    普通写法

    1. <style scoped>
    2. .empty1 {
    3. margin-top: 30px;
    4. margin-left: 30px;
    5. margin-right: 30px;
    6. background: #ffffff;
    7. box-shadow: 8px 16px 15px 0px rgba(35, 62, 183, 0.12);
    8. border-radius: 16px;
    9. border: 1px solid rgba(216, 221, 226, 0);
    10. }
    11. .empty2 {
    12. margin-top: 30px;
    13. margin-left: 30px;
    14. margin-right: 30px;
    15. background: #ffffff;
    16. box-shadow: 8px 16px 15px 0px rgba(35, 62, 183, 0.12);
    17. border-radius: 16px;
    18. border: 1px solid rgba(216, 221, 226, 0);
    19. }
    20. style>

    这样写的好处是 相同的业务组件布局都呈模块化的类聚在一起,方便阅读和查看。

    1. 页面的数据到更新也得动态绑定是怎么做到的?

    举个例子:

    1. let val1 = 2
    2. let val2 = 3
    3. let sum = val1 + val2
    4. console.log(sum) // 5
    5. val1 = 3
    6. sum = val1 + val2
    7. console.log(sum) // 仍然是 5

    如果我们更新第一个值,sum 不会被修改。

    那么我们如何实现这一点呢?没错,就是你想的那样,在数值变化时,随时运行我们的总和函数。然后 Vue 把这个过程优化,达到能我们代码开发的能力。

    3.动态变更组件样式

    在 style 里面进行动态修改

    在 class 里面进行动态修改

    4.package.json 中有说明注意点吗?

    在配置通过 package.json 文件导入的组件的时候,需要注意一下,我们的组件是只限于开发环境使用还是生产环境使用。比如我们常见的 eslint 就只会在开发的时候使用,所以导入的时候,只导入到开发配置中,能够有效的降低生产环境代码量。

  • 相关阅读:
    抖音视频评论批量下载软件|抖音数据抓取工具
    Unity AVPro Video使用和WebGL播放视频流
    MPLS VPN跨域C1方案 RR反射器
    C++ day3
    YOLOv5,YOLOv8添加ASFF(自适应空间特征融合)
    SpringCloud 微服务(三)-分布式事务问题
    分布式ID生成解决方案——雪花生成算法Golang实现
    [附源码]Python计算机毕业设计安庆师范大学校园互助平台
    两道相似的三维dp动态规划题
    猿创征文 |【C++】面向对象之微观部分——类的组成(下)
  • 原文地址:https://blog.csdn.net/develop_csdn/article/details/126172277