After learning the source code of element-plus, I learned a variety of writing methods

The cover of the article comes from Shenzhen Bay Bridge, very beautiful!

This article is after reading the source code and learning some new writing methods, which can also be used in normal business development. In my opinion, reading the source code can not only know the underlying principle of the framework, but also quickly troubleshoot and fix bugs when there are bugs. More importantly, reading the source code is like learning from excellent people and mastering new knowledge points that we have never understood. , to see how others write beautiful, reusable code.

operator!.

onMounted(() => {
  // 组合在一起,!. 就是“强制执行方法,然后再访问它的返回值”。
  const items = breadcrumb.value!.querySelectorAll(`.${ns.e('item')}`)
  if (items.length) {
    items[items.length - 1].setAttribute('aria-current', 'page')
  }
})onMounted(() => {
  // 组合在一起,!. 就是“强制执行方法,然后再访问它的返回值”。
  const items = breadcrumb.value!.querySelectorAll(`.${ns.e('item')}`)
  if (items.length) {
    items[items.length - 1].setAttribute('aria-current', 'page')
  }
})

 element-plus: Only the functions that control the behavior on the page exist in the vue file, and other logic processing functions are all in the ts file

Take value and monitor value change, do not use watch

const checkedValue = computed<CascaderValue>({
  get() {
    return cloneDeep(props.modelValue) as CascaderValue
  },
  set(val) {
    emit(UPDATE_MODEL_EVENT, val)
    emit(CHANGE_EVENT, val)
    if (props.validateEvent) {
      formItem?.validate('change').catch((err) => debugWarn(err))
    }
  },
})const checkedValue = computed<CascaderValue>({
  get() {
    return cloneDeep(props.modelValue) as CascaderValue
  },
  set(val) {
    emit(UPDATE_MODEL_EVENT, val)
    emit(CHANGE_EVENT, val)
    if (props.validateEvent) {
      formItem?.validate('change').catch((err) => debugWarn(err))
    }
  },
})

Add after the method call!

// 透传
  const carouselContext = inject(carouselContextKey)!// 透传
  const carouselContext = inject(carouselContextKey)!

In JavaScript, an exclamation point is added after the method call! It is usually used to enforce (Force execution) this method. Take an array as an example:- When using an array method without an exclamation mark, it will only return the result of the method, but will not change the array itself:

let arr = [1, 2, 3];
let newArr = arr.map(x => x * 2);

// newArr [2, 4, 6]
// arr 还是 [1, 2, 3], 数组本身没有变化let arr = [1, 2, 3];
let newArr = arr.map(x => x * 2);

// newArr [2, 4, 6]
// arr 还是 [1, 2, 3], 数组本身没有变化

After adding an exclamation point, the array itself will be modified:

let arr = [1, 2, 3];
arr.map!(x => x * 2);

// arr 变为 [2, 4, 6]
// 数组自身被修改了let arr = [1, 2, 3];
arr.map!(x => x * 2);

// arr 变为 [2, 4, 6]
// 数组自身被修改了

This is because methods such as the map of the array itself will not modify the original array, but adding an exclamation mark will force the modification of the array itself.

The same is true for other array methods, such as filter, forEach, sort, etc. If you do not add an exclamation mark, you will only return the result, and if you add an exclamation mark, you will modify the original array.

In general, the exclamation point ! is used to enforce a method that does not have side effects, produces side effects, and modifies the caller itself.

This is similar to the close-up operator (Bang pattern)! in Haskell, which is also used to generate computations with effects.

You need to be careful when using the exclamation mark, which may affect the predictability and data invariance of the program, but for the scene where the source data needs to be changed, using the exclamation mark can make the code more concise.

Use symbol for transparent transmission

provide(
      CASCADER_PANEL_INJECTION_KEY, // 透传下去,使用Symbol作为唯一值
      reactive({
        config,
        expandingNode,
        checkedNodes,
        isHoverMenu,
        initialLoaded,
        renderLabelFn,
        lazyLoad,
        expandNode,
        handleCheckChange,
      })
    )


export const CASCADER_PANEL_INJECTION_KEY: InjectionKey<ElCascaderPanelContext> =
  Symbol()


provide(
      CASCADER_PANEL_INJECTION_KEY, // 透传下去,使用Symbol作为唯一值
      reactive({
        config,
        expandingNode,
        checkedNodes,
        isHoverMenu,
        initialLoaded,
        renderLabelFn,
        lazyLoad,
        expandNode,
        handleCheckChange,
      })
    )


export const CASCADER_PANEL_INJECTION_KEY: InjectionKey<ElCascaderPanelContext> =
  Symbol()

enforced, affects the final value

Get component instance, call component custom event

const { emit } = getCurrentInstance()!

const model = computed({
    get() {
      return isGroup.value
        ? checkboxGroup?.modelValue?.value
        : props.modelValue ?? selfModel.value
    },

    set(val: unknown) {
      if (isGroup.value && isArray(val)) {
        isLimitExceeded.value =
          checkboxGroup?.max?.value !== undefined &&
          val.length > checkboxGroup?.max.value
        isLimitExceeded.value === false && checkboxGroup?.changeEvent?.(val)
      } else {
        emit(UPDATE_MODEL_EVENT, val)
        selfModel.value = val
      }
    },
  })
const { emit } = getCurrentInstance()!

const model = computed({
    get() {
      return isGroup.value
        ? checkboxGroup?.modelValue?.value
        : props.modelValue ?? selfModel.value
    },

    set(val: unknown) {
      if (isGroup.value && isArray(val)) {
        isLimitExceeded.value =
          checkboxGroup?.max?.value !== undefined &&
          val.length > checkboxGroup?.max.value
        isLimitExceeded.value === false && checkboxGroup?.changeEvent?.(val)
      } else {
        emit(UPDATE_MODEL_EVENT, val)
        selfModel.value = val
      }
    },
  })
  1. The getCurrentInstance() function can get the current component instance in setup().

  2. But calling getCurrentInstance() directly will return an optional (maybe) instance, which may be null.

  3. In order to ensure that the instance is obtained, use the ! non-null assertion operator to force conversion to a non-null instance.

  4. 所以 getCurrentInstance()! 确保了返回的实例不会是 null。

  5. 然后使用 ES6 解构赋值语法,从实例中取出 emit 方法。

  6. emit 方法用于在组件内部触发自定义事件。

  7. 这种方式避免了代码中出现隐式的 this,使代码更清晰可读。

  8. 是 Vue 3 Composition API 中常用的一种实例访问模式,用于在 setup() 中获取实例属性和方法。

unref与ref

unref 和 ref 都可以用来获取响应式对象的值,但是有以下几点关键区别:

  1. unref 是一个函数,ref 是创建ref对象的方法。unref直接返回值,ref会创建一个响应式的引用对象。

  2. unref 获取值,ref 创建值的引用。unref目的是获取值,ref是创建一个值的响应式引用。

  3. unref 参数可以是基础值或响应式对象,ref只接受基础值创建引用。unref的参数可以是基础类型的值,也可以是ref或reactive对象,它会返回对象的值或对象本身。ref只接受基础类型的值来创建一个响应式的引用对象。

  4. unref 使用场景是在组件逻辑中获取值,ref创建应用于模板的响应式引用。unref常用于组件逻辑中,需要获取响应式对象的原始值时使用。ref更多地在模板中使用,创建一个可以响应式跟踪的引用。

unref直接返回的值,不需要用.value访问

rAF & cAF

requestAnimationFrame 和 cancelAnimationFrame 是浏览器用来实现高性能动画的 API。它可以把每一帧的代码编排到浏览器的一次重绘中,避免频繁的重绘导致性能问题。

这两个函数在频繁重绘调用,可以减轻浏览器负担

比如:定时器

const isClient = typeof window !== "undefined";

export const rAF = (fn: () => void) =>
  isClient
    ? window.requestAnimationFrame(fn)
    : (setTimeout(fn, 16) as unknown as number)

export const cAF = (handle: number) =>
  isClient ? window.cancelAnimationFrame(handle) : clearTimeout(handle)
const isClient = typeof window !== "undefined";

export const rAF = (fn: () => void) =>
  isClient
    ? window.requestAnimationFrame(fn)
    : (setTimeout(fn, 16) as unknown as number)

export const cAF = (handle: number) =>
  isClient ? window.cancelAnimationFrame(handle) : clearTimeout(handle)

(event.target as HTMLElement).closest

const handleMouseDown = (event: MouseEvent) => {
  const target = (event.target as HTMLElement).closest('td')
  if (!target) return
  focusWithClick = true
}

const handleMouseUp = (event: MouseEvent) => {
  const target = (event.target as HTMLElement).closest('td')
  if (!target) return
  focusWithClick = false
}const handleMouseDown = (event: MouseEvent) => {
  const target = (event.target as HTMLElement).closest('td')
  if (!target) return
  focusWithClick = true
}

const handleMouseUp = (event: MouseEvent) => {
  const target = (event.target as HTMLElement).closest('td')
  if (!target) return
  focusWithClick = false
}

closest() 方法会沿着 DOM 树向上寻找匹配的选择器的第一个祖先元素,在这里是查找最近的 元素。所以整个代码的作用就是:- 获取点击事件的目标元素

  • 将其转化为 HTMLElement 类型
  • 在其祖先元素中查找最近的 td 元素
  • 并返回这个 td 元素这样可以方便地通过点击事件获取到对应的 td 元素进行后续操作。

Guess you like

Origin juejin.im/post/7255955134131503164