vue3代码优化

666 阅读4分钟

本文参考自哔哩哔哩李南江老师视频笔记

上一篇文章vue3项目搭建笔记

知识点

toRaw - 响应式对象转普通对象

如果我们的操作中操作了状态值,但我们不想要页面马上响应,可以使用该api

<div>
    {{state}}
    <el-button @click="onChangeState">点击state</el-button>
    <el-button @click="onChangeRaw">点击raw</el-button>
  </div>
  setup () {
    const init = {
      a: 1,
      b: 2
    }
    const state = reactive(init)
    const stateRaw = toRaw(state)
    console.log(stateRaw === init) //true,表示内存地址一致
    function onChangeState () {
      state.a++
      console.log('init->', init)
      console.log('state->', state)
      console.log('stateRaw->', stateRaw)
    }
    function onChangeRaw () {
      stateRaw.a++
      console.log('init->', init)
      console.log('state->', state)
      console.log('stateRaw->', stateRaw)
    }
    return { state, stateRaw, onChangeState, onChangeRaw }
  }

点击第一个按钮state发生变化,同时页面更新了,点击第二个,页面没有发生变化,但数据发生了改变。故我们可以使用该api做无需页面刷新的状态变更。

markRaw - 添加对象为不可转为响应式数据的标记

注意:是添加对象,基本数据类型不行,并且这种特性仅停留在根级别。

const data = { a: 1, b: { c: 2 } }
markRaw(data.b) // 该方法也返回原传入参数
const state = reactive(data)
function change () {
    // state.b.c++ // 该操作无法更新视图
    state.a++ // 该操作可以
}
const data = { a: 1, b: { c: 2 } }
markRaw(data)
const state = reactive(data.b)
function change () {
    state.c++ // 该操作可以
}

readonly, shallowReadonly - 不可变数据

  • readonly创建不可更改数据

const readonlyData = readonly({ a: 1, b: { c: 2 } })

那么你将无法修改readonlyData的任何参数

  • shallowReadonly 只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,类似const
const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2,
  },
})
// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改

customRef 自定义ref

该方法一般推荐独立文件编写,以use开头。

track用于追踪,trigger用于触发响应,set传入新值。

注意get里面尽量不要调用trigger,因为trigger会触发页面更新,页面更新会调用值的get方法,从而出现死循环。这块的优化单独放下面。

function useDataRef (value) {
  return customRef((track, trigger) => {
    return {
      get () {
        console.log('get', value)
        track(value.a)
        return value
      },
      set (newValue) {
        console.log('set', value)
        value = newValue
        trigger()
        return true
      }
    }
  })
}

const data = useDataRef({ a: 1 })
function change () {
    // data.value.a++ // 无法更新页面
    data.value = data.value.a + 1 // 可以更新页面
    // (个人见解)由此可见customRef构造的对象应该是和shallowRef差不多
    // 使用时请data.value = 来直接设置值
}

优化

逻辑抽离

  • 最简单的(这块只做参考,实际中可能有大量业务逻辑,可以考虑将非业务的剥离出来,然后再对业务二次封装使用)
// 优化前
setup () {
    const num1 = ref(0)
    function change1 () {
      num1.value++
    }
    const num2 = ref(100)
    function change2 () {
      num2.value++
    }
    return { num1, change1, num2, change2 }
}
// 优化后
// 这块代码可以单独文件编写、或者编写于script内
function useNum (init: number) {
    const num = ref(init)
    function change () {
      num.value++
    }
    return { num, change }
}
// ...
setup () {
    const { num: num1, change: change1 } = useNum(0)
    const { num: num2, change: change2 } = useNum(100)
    return { num1, change1, num2, change2 }
}
  • 稍微复杂一点,参考自ahooks的useRequest
<el-button @click="run" :loading="loading">run</el-button>
// ...
import { ref, reactive, onMounted, watchEffect } from 'vue'
function useRequest<R = any> (url: string, option?: Request) {
  let result = reactive<R>(null) // 管理数据
  const loading = ref(false) // 管理loading
  async function run (opt?: Request) { // 调用接口请求
    loading.value = true
    const response = await fetch(url, { // 请使用axios
      ...option,
      ...opt
    })
    // 测试loading的延迟,实际情况不要
    // await new Promise(resolve => {
    //   setTimeout(() => {
    //     resolve()
    //   }, 2000)
    // })
    loading.value = false
    // ...处理逻辑
    result = response
    return response
  }
  function reset () {
    result.value = undefined
  }
  return { result, run, reset, loading }
}
// ...
setup () {
    const { result, loading, run } = useRequest('/queryUserInfo', {
      method: 'post'
    })
    // 初始话时就调用,其实也可以封装成useRequest的一个参数
    onMounted(() => {
      run().then(resp => {
        console.log(resp)
      })
    })
    watchEffect(() => {
      console.log(loading.value)
      console.log(result)
    })
    return { loading, run }
}
  • 我们在使用element-plus(其它ui组件也一样)的部分组件时,由于官方封装要考虑大众,我们可以封装一层方便自己使用的hook,比如参考ahooks的useAntdTable来封装element-plus的hook,这里就不做演示

方法封装

  • 按传统方式封装axios(这个大家都使用过,举例出来是不想大家受到hook的限制,有过一段时间使用react的hooks时,什么方法都是自定义hooks,这样其实思想就受到了限制。就当水字数)
import axios, { AxiosRequestConfig } from 'axios'

const instance = axios.create({
  baseURL: '',
  timeout: 1000,
  headers: { 'X-Custom-Header': 'foobar' }
})
const apiRequest = (url: string, options?: AxiosRequestConfig) => {
  return instance.request({
    url,
    ...options
  })
}
export default apiRequest
  • customRef的其它用处,参考自李南江老师视频的一个例子,搬砖
function useDataRef (value) {
  return customRef((track, trigger) => {
    const { run } = useRequest('/url') // 新增此处开始
    run().then(resp => {
      value = resp
      trigger() // 可以在这调用用了更新页面
    }) // 新增此处结束
    return {
      get () {
        console.log('get', value)
        track()
        return value
      },
      set (newValue) {
        console.log('set', value)
        value = newValue
        trigger()
      }
    }
  })
}

结语

  • 可以使用toRaw、markRaw、shallowReactive、shallowRef优化性能
  • 可以使用readonly, shallowReadonly规范不变值
  • 可以自定义hook优化代码的结构,使代码更加优雅