note
  • Introduction
  • algorithm
    • Array
    • BinarySearch
    • Bits
    • Design
    • DFS
    • DP
    • DP_bag
    • DP_stock.md
    • Hash
    • Heap
    • NodeList
    • Number
    • SlideWindow
    • Sort
    • Stack
    • String
    • Tree
  • Backend
    • express
    • koa2
    • node
    • npm
    • npx
  • db
    • mongoDB
  • Frontend
    • CSS
      • BFC
      • flex
      • layout
      • less
      • middle
      • position
      • sass
      • selector
      • 动画相关属性
      • 响应式页面
      • 层叠上下文
      • 隐藏元素
    • JS
      • Array
      • async
      • DOM
      • ES6
      • JS-军规
      • macrotask-microtask
      • practice
      • RegExp
      • this-prototype-inherit
      • type-convert
      • 前端请求
      • 浏览器加载
      • 浏览器窗口间通信
    • TS
      • note
    • Vue
      • practice
      • Component-Communicate
      • Component
      • lifecycle
      • note
      • Pinia
      • practice
      • Vue-cli
      • Vue-Router
      • Vue-Source
      • Vue2
      • Vue3
      • Vuex
    • cross-domain
    • webpack
    • 从前端角度理解缓存
    • 前端概念
    • 页面性能分析与优化
    • 页面渲染机制
  • Linux
    • basic-command
    • docker
    • java-env
    • learn
    • manage-command
    • nginx
    • Shell
    • ssh
    • tmux
    • vi
  • chrome-devtools
  • git
  • Jenkins
Powered by GitBook
On this page
  1. Frontend
  2. Vue

practice

Vue

template

  • 把一个<template> 元素当做不可见的包裹元素,并在上面使用 v-if, 最终的渲染结果将不包含 <template>

props

//对于多类型场景
<template>
  <button :style="computedWidth">{{ computedWidth }}</button>
</template>

<script>
  export default {
    props: {
      width: [String, Number]
    },
    computed: {
      computedWidth () {
        let obj = {}
        if (typeof this.width === 'string') o.width = this.width
        if (typeof this.width === 'number') o.width = this.width + 'px'
        return obj
      }
    }
  }
</script>

watch

created(){
  this.fetchPostList()
},
watch: {
  searchInputValue(){
    this.fetchPostList()
  }
}
//改成
watch: {
  searchInputValue:{
    handler: 'fetchPostList',
    immediate: true
  }
}
//优化deep
watch: {
  obj: {
    handler(newName, oldName) {
      console.log('obj.a changed')
    },
    deep: true
  }
}
//改成
watch: {
  'obj.a': {
    handler(newName, oldName) {
      console.log('obj.a changed');
    },
  }
}
//全局watch定义的时候需要注销watch
const unWatch = app.$watch('text', (newVal, oldVal) => {
  console.log(`${newVal} : ${oldVal}`)
})
unWatch()

computed

  • 数据缓存、依赖响应式数据、减少模板中的计算逻辑

  • 最佳实践是computed定义一个属性,这个属性依赖多个响应式,watch这个属性

is && keep-alive

<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

v-slot

//具名插槽
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>
<!-- 默认插槽也可不用加上template和v-slot -->
  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>
  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

//作用域插槽
//子组件抛出自己的状态,父组件在外面使用
<!-- current-user组件 -->
<span>
  <slot :user="user">
    {{ user.lastName }}
  </slot>
</span>

<!-- 父级组件通过自定义名称访问子级作用域 -->
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

<!-- 支持缩写和解构 -->
<current-user>
  <template #default="{ user = { firstName: Gust } }">
    {{ user.firstName }}
  </template>
</current-user>

mixins

  • Vue.extend策略同mixins相同

  • 混入属性发生冲突时,以组件数据优先(浅合并第一层属性)

  • 混入方法发生冲突时,会将函数合并为一个数组,优先执行混入方法,其次执行组件方法

    //table.js
    import mixin from '@/mixin/table'
    export default {
        mixins: [mixin]
        ...
    }
    //mixin对象只应该被调用一次,以下每个组件都打点,代码改到根实例中,通过this.$root访问
    Vue.mixin({
    	mounted() {
    		console.log('xxxxxxxxxxxxxx')
    	},
    })

$options

  • 静态属性传给$options而不是data或computed

export default {
  name: 'Jack',
  age: 33
}
component.$options.phone
component.$options.city

自定义事件事件命名

  • 自定义事件事件名为小写或-分隔

v-once

  • 组件包含大量静态内容时,可使用v-once来标记,缓存静态内容

$forceUpdate强制更新view

动态属性名

<a :[name]="url"> ... </a>
<img :[sss]="/img/test.png"/>
<img :[change1()]="change2()"/>

data: {
  name: 'href',
  sss: 'src'
}

监听页面刷新和关闭

//beforeDestroy 只能监听到页面间的跳转,无法监听到页面刷新和关闭标签页
//页面加载时只执行onload
//页面关闭时先onbeforeunload,再onunload
//页面刷新时先执行onbeforeunload,然后onunload,最后onload
created() {
  this.initCart()
  window.addEventListener('beforeunload', this.updateHandler)
},
beforeDestroy() {
  this.finalCart()
  window.removeEventListener('beforeunload', this.updateHandler)
},
methods: {
  updateHandler() {
    this.finalCart()
  },
  finalCart() {
    //...
  }
}

修改子组件样式

<style>
/*添加要覆盖的样式*/
</style>
<style scoped>
/* 本地样式 */
</style>
<style lang="scss">
.box >>> input {
  width: 166px;
  text-align: center;
}
//像SASS之类的预处理器无法正确解析>>>,用 /deep/
.box {
  /deep/ input {
    width: 166px;
    text-align: center;
  }
}
</style>

$nextTick

  1. 在mounted生命周期执行DOM操作

  2. 在数据变化后需要进行基于DOM结构的操作

<template>
	<div class="app">
    <div ref="contentDiv">{{content}}</div>   ---After NextTick
    <div>在nextTick执行前获取内容:{{content1}}</div>   ---Before NextTick
    <div>在nextTick执行之后获取内容:{{content2}}</div> ---After NextTick
    <div>在nextTick执行前获取内容:{{content3}}</div>   ---Before NextTick
  </div>
</template>

<script>
export default {
	name: 'App',
	data: {
		content: 'Before NextTick',
		content1: '',
		content2: '',
		content3: '',
	},
	methods: {
		changeContent() {
			this.content = 'After NextTick'
			this.content1 = this.$refs.contentDiv.innerHTML
			this.$nextTick(() => {
				this.content2 = this.$refs.contentDiv.innerHTML
			})
			this.content3 = this.$refs.contentDiv.innerHTML
		},
	},
	mounted() {
		this.changeContent()
	},
}
</script>

基础组件全局注册

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径
  './components',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)
  // 获取组件的 PascalCase 命名
  const componentName = upperFirst(
    // 获取和目录深度无关的文件名
    camelCase(fileName.replace(/^\.\//, '').replace(/\.vue/, ''))
  )
  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根
    componentConfig.default || componentConfig
  )
})

element-UI related

  • 禁止浏览器Auto complete

//设置 <el-input/> 为只读模式
//在focus事件中去掉只读模式
<el-input
	type="text"
	v-model="addData.loginName"
	readonly
	@focus="handleFocusEvent"
/>

methods: {
	handleFocusEvent(event) {
		event.target && event.target.removeAttribute('readonly')
	}
}
  • 阻止<el-form>默认提交<el-form @submit.native.prevent>

  • 根据业务合并el-table中的行和列

<template>
	<el-table
		:data="formattedList"
		:span-method="handleColspanMethod"
	>
	...
	</el-table>
</template>
<script>
import Api from '@/api/assign'
export default {
	data() {
		return {
			list: [], // 后台返回的数据
			formattedList:[], // 格式化后的数据
			spanArr: [], // 保存要合并的行列数据
		}
	},
	created() {
		this.getList()
	},
	methods: {
		getList() {
			Api.fetchList().then(response => {
				this.list = response.data.data
				// 格式化数据
				this.formattedList = this.formatArray(this.list)
				// 获取合并位置
				this.getSpanArr()
			})
		},
		/**
		* 格式化数组
		* {Array} sources 源数组
		* {Array} arrayed 格式化后的数组
		* return 返回格式化后的数组
		*/
		formatArray: function f(sources, arrayed) {
			if (!sources) return []

			const arr = arrayed || []
			const len = sources.length

			for (let i = 0; i < len; i++) {
				const children = sources[i].list || []
				arr.push(sources[i])
				if (children.length > 0) {
					f(sources[i].list, arr)
				}
			}
			return arr
		},
		/**
		* 获取合并位置信息
		*/
		getSpanArr() {
			// 重置 spanArr,因为翻页的时候数据就变了
			// 之前的数据如果不清空,其他页也会受影响
			this.spanArr = []
			const data = this.formattedList
			if (!data || data.length <= 0) return
			// 遍历
			for (let i = 0; i < data.length; i++) {
				if (i === 0) {
					this.spanArr.push(1)
					// 其实就是行索引
					this.pos = 0
				} else {
					// 如果当前对象和上一个对象的 appKey 相等
					// 说明需要合并
					if (data[i].appKey === data[i - 1].appKey) {
						this.spanArr[this.pos] += 1
						this.spanArr.push(0)
					} else {
						this.spanArr.push(1)
						this.pos = i
					}
				}
			}
		},
		/**
		* 处理跨行列合并
		*/
		handleColspanMethod({ row, column, rowIndex, columnIndex}) {
			if (columnIndex < 2) {
				const _spa = this.spanArr[rowIndex]
				const _row = _spa ? _spa : 0
				const _col = _row > 0 ? 1 : 0
				return {
					rowspan: _row,
					colspan: _col
				}
			}
		}
	}
}
</script>

Vue Router

$router vs $route

  • this.$router指路由器,只写,this.$route指当前路由,只读

regex path

  • 当使用一个通配符时,$route.params内会自动添加一个名为pathMatch参数。它包含URL通过通配符被匹配的部分

// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
  • 使用了push时,如果提供的path不完整,则params会被忽略,需要提供路由的name或手写完整的带有参数的path

const userId = '123'
router.push({ name: 'user', params: { userId }})  // -> /user/123
router.push({ path: `/user/${userId}` })          // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

命名视图

  • 单页多路由,命名视图:router-view可设置名字,如果router-view没有设置名字,那么默认为default

<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

路由参数设置为组件属性

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },
    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      //props: { default: true, sidebar: false }
      props: true
    }
  ]
})

// URL /search?q=vue 会将 {name: 'vue'} 作为属性传递给 SearchUser 组件
const router = new VueRouter({
  routes: [
    { path: '/search', component: SearchUser, props: (route) => ({ name: route.query.q }) }
  ]
})
  • 不同路由的组件复用

// 1 使用watch来监控是否是同一个路由

//way 2
created () {
  this.getPost(this.$route.params.id)
},

// 3 设置key(推荐)
//<router-view :key="$route.fullpath"></router-view>

路由设置参数

  • 路由设置有参数时,若跳转页面后再通过返回键返回时,路由会保留有参数,若通过push跳转返回,则不会保留该参数,这在第三方调用模块传参时需要注意

路由异步加载

{
  path: '/admin',
  name: 'admin-dashboard',
  component:require('@views/admin').default
}
// or
{
  path: '/admin',
  name: 'admin-dashboard',
  component:() => import('@views/admin')
}

刷新当前页面

<router-link :to="url" @click.native="refreshView">页面</router-link>
<router-view v-if="showView"/>

<script>
export default {
  data () {
    return {
      showView: true
    }
  },
  methods: {
    refreshView () {
      this.showView = false
      this.$nextTick(() => {
        this.showView = true
      })
    }
  }
}
</script>

刷新页面参数丢失

  • localStorage、sessionStorage、cookie

//1 使用编程导航 
this.$router.push({ name: '模块名称', query: { // 各参数 } }) 方式传参

//2 localStorage
// router.js
{
  path: 'paramsMode/:aka',
  name: 'paramsMode',
  component: ParamsMode
}

//ParamsMode.vue
<script>
export default {
  data () {
    return {
      testData: {}
    }
  },
  created () {
    const tempData = localStorage.getItem('tempData')
    if (tempData) {
      this.testData = JSON.parse(tempData)
    } else {
      this.testData = this.$route.params
      localStorage.setItem('tempData', JSON.stringify(this.$route.params))
    }
  },
  beforeDestroy () {
    localStorage.removeItem('tempData')
  }
}
</script>

Vuex

刷新页面Vuex数据丢失

//将状态保存在localStorage、sessionStorage
import createPersistedState from 'vuex-persistedstate'

const store = new Vuex.Store({
  plugins: [
    createPersistedState()
  ]
})

//将状态保存在cookie
import { Store } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import * as Cookies from 'js-cookie'

const store = new Store({
  plugins: [
    createPersistedState({
      storage: {
        getItem: key => Cookies.get(key),
        setItem: (key, value) => Cookies.set(key, value, { expires: 3, secure: true }),
        removeItem: key => Cookies.remove(key)
      }
    })
  ]
})

Vuex动态加载

<template>
  <div>
  	<button @click="show">show</button>
  </div>
<template/>
  
<script>
  const getTestModule = () => import('./testModule.js')
</script>
const getTestModule = () => import('./testModule.js')
export default {
	methods: {
		show() {
			getTestModule().then((testModule) => {
				this.isModuleRegistered = true
				this.$store.registerModule('testModule', testModule)
				this.$store.dispatch('testModule/load')
			})
		},
	},
	beforeDestroy() {
		if (this.isModuleRegusterred) {
			this.$store.unregisterModule('testModule')
		}
	},
}

自动注册vuex模块

const requireModule = require.context('./modules', false, /\.js$/)
const modules = {}

requireModule.keys().forEach(fileName => {
    const moduleName = fileName.replace(/(\.\/|\.js)/g, '')
    modules[moduleName] = {
        namespaced: true,
        ...requireModule(fileName).default
    }
})

export default modules

/* index.js */
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'

Vue.use(Vuex)
export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules
})

mapState / mapMutations / mapGetters / mapActions

import {mapState, mapGetters, mapActions} from 'vuex'

computed: {
  ...mapState(['count']),
  ...mapGetters(['evenOrOdd'])
},

methods: {
  ...mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync'])
}

事件常量代替mutation类型,放到单独文件中

//mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

//store.js
import vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types.js'

const store = new Vuex.Store({
  state: {...},
  mutation: {
    [SOME_MUTATION](state) {
      //...
    }
  }
})

module

  • 开启namespaced:true

  • createNamespacedHelpers

PreviousVueNextComponent-Communicate

Last updated 2 years ago

el-scrollbar