开发中学到的碎片知识整理(持续更新中)

192 阅读5分钟

1、环境配置:

实现原理:

采用 node.js 顶层对象中的 process.env(进程环境,返回一个包含用户环境信息的对象)属性,根据不同的配置文件(.env.xxx)来区分和切换环境

配置文件:.env.xxx

// .env.dev
VUE_APP_ENV = development
VUE_APP_XXX_SERVE = "https://xxxxx.xxx.com/xxxxx"
// .env.pro
VUE_APP_ENV = production
VUE_APP_XXX_SERVE = "https://xxxxx.xxx.com/xxxxx"

package.json:

{
    "scripts":{
		"dev":"vue-cli-service serve --mode dev",
		"prod":"vue-cli-service serve --mode pro"
	}
}

之后输入命令 npm run xxx 就可以实现对应环境地址的切换啦~

2、vue2 中防抖的做法:

data() {
  return {
    timer: null	// 定义一个变量,用于接收、解除定时器
  }
},

watch: {
  xxx(val) {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    this.timer = setTimeout(() => {
      // 业务逻辑,例如 filter、axios 查询请求等等
    }, 1000)
  }
},

3、Git 操作汇总

# 查看远程仓库
git remote -v 

# 添加远程仓库
git remote add <remote name> <ssh url> 
    
# 更名远程仓库
git remote rename <remote name> <new name>

# 拉取远程仓库内容
git pull <remote name> <branch name>

# 推送到远程仓库
git push <remote name> <branch name>
    
# 推送新分支到远程仓库
git push --set-upstream <remote name> <branch name>
    
# 删除本地分支
git branch --delete <branch name>
    
# 拉取远程仓库中的新分支
git checkout -b <newBranchName>
git branch --set-upstream-to=<remote name>/<newBranchName>
git pull <remote name> <newBranchName>
    
# 回退 commit
git revert -n <commit version>

# 缓存修改(切换分支、合并代码时非常有效)
git stash	// 将修改记录缓存到堆栈中
git stash save "message"
git stash list // 查看当前 stash 堆栈中的内容
git stash pop // 将当前 stash 中的内容弹出,并引用到当前分支对应的工作目录中(会将最近一次保存的 stash 删除)
git stash apply // 将堆栈中的内容应用到当前目录(不会从堆栈中删除 stash 记录)
git stash drop "name" // 从堆栈中删除删除
git stash clear
git stash show // 查看堆栈中最新保存的 stash 和当前目录的差异

4、Vuex 模块化:

最外面的 index:

const countOptions = {
    // 如果想起别名,必须加上(默认为 false)
    namespaced: true,
    
    actions: {...},
    mutations: {...},
    state: {...},
    getters: {...},
}

export default new Vuex Store({
    modules: {
        // 使用别名
        countAbout: countOptions.
        // countOptions
    }
})

使用数据:

<template>
    <p>
        {{ sum }}
    </p>
    <button @click="increment"></button>
</template>

<script>
	import { mapState } from 'vuex'
	export default {	
		computed: {
		    ...mapState('countAbout', ['sum', ...])	// 数组
             ...mapGetters('countAbout', ['bigSum', ...])	// 数组
		},
         methods: {
         	...mapMutations('countAbout', { increment: 'JIA', ... }) // 对象
        	...mapActions('countAbout', { incrementOdd: 'JIAOdd', ... }) // 对象      
         }
	}
</script>

5、vuedraggable 使用经验分享(行拖动)

1、安装:

npm i -S vuedraggable

2、导入并注册

import draggable from 'vuedraggable' // 导入的是一个组件,需要注册

export default {
  components: {
      draggable,
  },
};

3、使用 draggable

<template>
  <table>
    <!-- 如果要添加表头,目前我的做法是使用 draggable 进行包装,并通过 disabled 设置为不可拖拽;否则会出现显示问题 -->
    <draggable :disabled="true">
      <tr>
        <th>AAA</th>
        <th>BBB</th>
        <th>CCC</th>
      </tr>
    </draggable>
    <draggable
      v-model="listForDrag" (使用 v-model 绑定用于展示数据的列表)
      animation="150" (填入拖拽动画的延时时长)
      handle=".move" (类似选择器只有带 move 类的元素才可以被拖动)
      @start="onStart" (拖拽开始事件)
      @end="onEnd" (拖拽结束事件)
      :move="checkMove" (拖拽过程事件)> 
      <tr v-for="item in listForDrag" :key="item.id" class="move">
        <td>{{ item.aaa || '-' }}</td>
        <td>{{ item.bbb || '-' }}</td>
        <td>{{ item.ccc || '-' }}</td>
      </tr>
    </draggable>
  </table>
</template>

<script>
import draggable from 'vuedraggable'
export default {
  components: {
    draggable,
  },

  data() {
    return {
      drag: false, // 记录是否正在执行拖拽
      listForDrag: [], 
    };
  },

  methods: {
    // 可以监听到调用拖拽的对象信息
    checkMove(e){
      return true;
    },
    onStart() {
      this.drag = true;
    },
    onEnd() {
      this.drag = false;
    },
  },
};
</script>

6、ES6 运算符

0、升级 babel

该语法在 babel 版本不支持的情况下使用会报错

1、空值合并操作符(??)

x ?? y,当 x 为 null 或者 undefined 时,返回 y,否则返回 x

const nullValue = null
const emptyText = ''
const someNum = 42

console.log(nullValue ?? 'valA 的默认值')	// valA 的默认值
console.log(emptyText ?? 'valB 的默认值')	// '' (空字符串虽然是空值,但不是 null / undefined)
console.log(someNum ?? 'valC 的默认值')		// 42

2、可选链操作符(?.)

允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。在引用为空(null / undefined) 的情况下不会引起错误,短路时返回值是 undefined

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
}

console.log(adventurer.dog?.name)					// undefined
console.log(adventurer.someNonExistentMethod?.())	// undefined
a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

3、空赋值运算符(??=)

x ??= y,仅在 x 为 null / undefined 时对其赋值

let a = 0
a ??= 1
console.log(a)	// 0

let b = null
b ??= 1
console.log(b)	// 1

7、Element 中 Switch 无法正常更新的问题

开发中,switch 的 v-model 需要绑定深层次的属性 inherit

const obj = [{
  name: 'xxx',
  children: [{
    inherit: false
  }] 
}]

问题:常规的写法并不会根据 switch 的动作实时更新开关状态(点击开关没反应,必须刷新才有效果)

<el-switch
  v-model="row.inherit"
</el-switch>

解决方法:需要使用 vue 中自带的 $set 函数来辅助更新数据

<el-switch
  v-model="row.inherit"
  @change="controlSwitch($event, row)">
  <!-- $event 传递的是更改的值 -->
</el-switch>

<script>
  export default {
    methods: {
      controlSwith(val, row) {
        // this.$set(target, key, value)
        this.$set(row, row.inherit, val)
      }
    }
  }
</script>

8、打开的标签页太多,滚动条太细不好拖动?

【VSCode 设置】-搜索 【Wrap Tabs】-选中

9、ElementUI 中使用嵌套 Dialog 或者连续使用 Dialog 并嵌套 Select 时,Select 层级过低问题

<!-- 为每个 select 都设置以下属性 -->
<el-select :popper-append-to-body="false" popper-class="select-popper">
/* 解决 dialog 嵌套 select 显示层级不正常问题 */
.el-select /deep/ .select-popper {
  z-index: 8888 !important;
  top: auto !important;
  left: auto !important
}

10、vue3 中子组件修改对象形式的 props

<!-- 父组件中引用子组件时使用 v-model -->
<SheetSectionTableButtons
	v-model:editingFieldValue="editingFieldValue"
/>
// 子组件中用 defineProps 接收 v-model
const props = defineProps<{
  editingFieldValue?: Record<string, FieldType>
}>()

// 子组件中定义修改 props 的自定义事件
const emit = defineEmits<{
  (e: 'update:editingFieldValue', newEditingFieldValue: Record<string, FieldType>): void
}>()

// 使用另一个对象更新父组件中的 v-model
function fn() {
    const newEditingFieldValue = { ...props.editingFieldValue }
    newEditingFieldValue[fieldId].fieldValue = useCopyNames(props.fieldValue, value)
    emit('update:editingFieldValue', newEditingFieldValue)
}

注意:虽然对象形式的 props 可以在子组件中直接被修改,但是并不推荐

cn.vuejs.org/guide/compo…

11、antd 表单校验时,即使填了值也爆红

检查与 AForm 绑定的 model 对象的 key 值是否与 AFormItem 绑定的 name 值相等

12、hover 过快导致一些使用上的不便

在需要对 hover 进行延迟的地方加上:

<li
    @mouseenter="setHoverDelay()"
    @mouseleave="removeHoverDelay()" />

<script setup lang="ts">
const timer = ref(null)

function setHoverDelay (menu) {
  if (timer.value) {
    clearTimeout(timer.value)
  }
  timer.value = setTimeout(() => {
    data.selectedMenu = menu
  }, 300)
}

function removeHoverDelay () {
  if (timer.value) {
    clearTimeout(timer.value)
  }
}
</script>

13、el-table 仅修改一行更新 tableData 但无法更新 DOM?

<el-table
  :tableData="tableData"
  :key="refreshFlag">
</el-table>

<script>
data () {
  return {
    refreshFlag: false,
  }
},  
watch: {
  tableData: {
    handler (val) {
      if (val) {
        this.refreshFlag = !this.refreshFlag
      }
    },
    deep: true
  }
}
</script>

14、ESLint 保存时自动修正格式

"eslint.enable": true,
  "eslint.run": "onType",
  "eslint.options": {
      "extensions": [
          ".js",
          ".vue",
          ".jsx",
          ".tsx"
      ]
  },
  "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
  }

15、El-checkbox 点击两次才切换,或者点击根本不切换

使用 :value 而非 v-model

16、Element 下载 Excel 文件

如果有 axios 拦截器,记得放行:

if (response.data instanceof Blob) {
  return response
}
<el-upload
  drag
  action=""
  :show-file-list="false"
  :before-upload="isExcel"
  :http-request="importExcel">
</el-upload>
<el-button type="primary" icon="el-icon-download" @click="export2Excel(true)">下载模板</el-button>
// 判断是否是 Excel 文件(.xlsx 后缀)
isExcel (file) {
  if (file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
    this.excelFile = file
    return true
  } else {
    errorMsg('请上传 .xlsx 后缀的 excel 文档')
    return false
  }
},
    
/**
  * 导入 Excel
  * 1、请求头:multipart/form-data
  * 2、格式正确响应:response.data.type === 'application/json'
  * 3、格式错误响应:response.data.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  * 4、上传成功后需要等待几秒后再刷新
  */
async importExcel (event) {
  const formData = new FormData()
  formData.append('excelFile', event.file)
  await $http.post('api地址', formData, {
    headers: {
      'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryVCFSAonTuDbVCoAN'
    },
    responseType: 'blob'
  }).then(
    response => {
      if (response.data.type !== 'application/json') {
        warningMsg('上传失败啦!请接收失败原因')
        saveAs(
          new Blob([response.data],
            { type: 'application/octet-stream' }),
          'Excel 文件上传失败原因.xlsx'
        )
      } else {
        successMsg('上传成功!正在处理,请稍后刷新查看')
      }
    })
},
    
/**
  * 导出成员列表为 Excel
  * 1、响应类型:blob
  * 2、使用插件 { savaAs } = file-saver 解析 blob
  */
async export2Excel (template = false) {
  await $http.get('api地址', {
    responseType: 'blob'
  }).then(
    response => {
      saveAs(
        new Blob([response.data],
          { type: 'application/octet-stream' }),
        '下载文件名.xlsx'
      )
    },
    reject => {
      errorMsg(`导出 Excel 异常:${reject.message ?? '未知错误'}`)
    })
},

17、TS项目中引入 JSON 文件报错

找不到模块../lib/pca-code.json。请考虑使用 --resolveJsonModule 导入带 .json 扩展的模块。ts(2732)

处理方法:在根目录的 shims-vue.d.ts 文件中加入以下代码

declare module '*.json' {
  const value: any;
  export default value;
}

18、如何 inject 一个函数

父组件:

provide('函数名', 目标函数)

子组件:

const 函数名 = inject('函数名', Function, true)

19、动态绑定 SRC 图片不显示的问题

因为动态添加src被当做静态资源处理了,没有进行编译,使用require定义之后,就可以动态使用了。

<img :src="require(src)">

20、如何批量使用 axios 发送请求?

const requestList = []
roleList.forEach(role => {
  const request = () => {
    return axios.post('/api', {
      ...params
    })
  }
  requestList.push(request())
})

const results = await Promise.allSettled(requestList)

21、普通链接转换为迅雷下载

可以类比其他应用的超链接

<template>
  <div class="container">
    <a href="https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-35-Thin.zip">阿里巴巴普惠体(普通下载)</a>
    <br>
    <br>
    <a data-thunder href="https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-35-Thin.zip">阿里巴巴普惠体(迅雷下载)</a>
  </div>
</template>

<script lang="ts" setup>
import { onMounted } from 'vue';

onMounted(() => {
  const links = Array.from(document.querySelectorAll('a[data-thunder]')) as HTMLAnchorElement[]
  for (const link of links) {
    // 迅雷链接编码:AAbase64ZZ,btoa:将二进制字符串编码为 base64 字符串
    const base64 = btoa(`AA${link.href}ZZ`)
    link.href = `thunder://${base64}`
  }
})
</script>

22、跨页签通信方案之 BroadcastChannel

<template>
  <div class="container">
    <button @click="PostMessage">PostMessage</button>
    <iframe src="http://localhost:3000/"></iframe>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref } from 'vue';

const bc = ref(new BroadcastChannel('centerChannel'))

onMounted(() => {
  bc.value.addEventListener('message', (e) => {
    if (window.top !== window) {
      console.log('iframe', e)
    } else {
      console.log('origin', e)
    }
  })
})

function PostMessage () {
  bc.value.postMessage({
    type: 'castMessage',
    content: '哈哈哈',
  })
} 
</script>