Skip to content

vue

动态参数(动态方法名)

https://vuejs.org/guide/essentials/template-syntax.html#dynamic-arguments

html
<a v-on:[eventName]="doSomething"> ... </a>

<!-- shorthand -->
<a @[eventName]="doSomething">

代理的代理

js
const raw = {}
const proxy = reactive(raw)

// proxy is NOT equal to the original.
console.log(proxy === raw) // false

// 代理的代理 与 代理是相同的。。。。
// calling reactive() on the same object returns the same proxy
console.log(reactive(raw) === proxy) // true

// calling reactive() on a proxy returns itself
console.log(reactive(proxy) === proxy) // true

可写计算属性

https://vuejs.org/guide/essentials/computed.html#writable-computed

vue
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // Note: we are using destructuring assignment syntax here.
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

组件内哪个元素接收 class 类

html
<my-component class="baz"></my-component>


<!-- my-component template using $attrs -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>

watch 和 watchEffect 区别

https://vuejs.org/guide/essentials/watchers.html#deep-watchers

watch 是监视某个确定的响应式数据。并且只会在数据改变之后触发回调。

watchEffect 是只有回调内的响应式数据改变就会重新触发回调。并且初始化的时候会触发一次回调函数。

watch 的回调函数执行时机

https://vuejs.org/guide/essentials/watchers.html#callback-flush-timing

默认情况下,回调函数的触发时机在组件更新之前。这意味着在回调函数内获取组件的数据是还未更新的。

获取真实的 dom 元素

html
<script setup>
import { ref, onMounted } from 'vue'
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

获取真实的 dom 元素数组

https://vuejs.org/guide/essentials/template-refs.html#refs-inside-v-for

组件内触发方法

html
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

动态组件

html
<!-- Component changes when currentTab changes -->
<component :is="tabs[currentTab]"></component>

大小写不敏感

html 的标签和属性命名是不敏感的。这意味着浏览器会断定任何大写字符为小写字符。

这意味着你在模板内使用大小驼峰命名、v-on 事件名,需要将他们转为烧烤串命名法。

js
// camelCase in JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}

<!-- kebab-case in HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

注册全局组件

js
import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

组件绑定多个属性

js
const post = {
  id: 1,
  title: 'My Journey with Vue'
}

<BlogPost v-bind="post" />

<BlogPost :id="post.id" :title="post.title" />

组件属性接收

html
<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

export default {
  props: ['foo'],
  setup(props) {
    // setup() receives props as the first argument.
    console.log(props.foo)
  }
}

单向数据流动

https://vuejs.org/guide/components/props.html#one-way-data-flow

js
const props = defineProps(['foo'])

// ❌ warning, props are readonly!
props.foo = 'bar'

方式一。将传入数据作为初始值,作为组件内部数据,传入数据改变不会触发视图更新。

js
const props = defineProps(['initialCounter'])

// counter only uses props.initialCounter as the initial value;
// it is disconnected from future prop updates.
const counter = ref(props.initialCounter)

方式二。将传入的数据稍作转换

js
const props = defineProps(['size'])

// computed property that auto-updates when the prop changes
const normalizedSize = computed(() => props.size.trim().toLowerCase())

注意:改变传入的对象、数组类型数据。

虽然子组件不支持直接改变对象、数组的引用地址,但是可以改变对象、数组的属性。

一般还是建议用 emit 改变。除非父子组件之间有强耦合关系。

触发事件验证

https://vuejs.org/guide/components/events.html#events-validation

html
<script setup>
const emit = defineEmits({
  // No validation
  click: null,

  // Validate submit event
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

处理 v-model 修饰符

https://vuejs.org/guide/components/events.html#usage-with-v-model

html
<MyComponent v-model.capitalize="myText" />

const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

console.log(props.modelModifiers) // { capitalize: true }

<MyComponent v-model:title.capitalize="myText">

const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }

v-on 监听器继承

https://vuejs.org/guide/components/attrs.html#v-on-listener-inheritance

@click 会绑定到组件的根元素上,若触发会触发父组件的 onClick 方法。若子组件也绑定有方法,那么都会触发。

html
<MyButton @click="onClick" />

将属性精确到组件某个元素上

https://vuejs.org/guide/components/attrs.html#disabling-attribute-inheritance

html
<script>
// use normal <script> to declare options
export default {
  inheritAttrs: false
}
</script>

<script setup>
// ...setup logic
</script>

在 js 中获取落空属性

https://vuejs.org/guide/components/attrs.html#accessing-fallthrough-attributes-in-javascript

html
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

有哪些封装好比较好用的钩子?

请求钩子,统一封装请求的状态,每一个请求都有对应的加载、错误状态,写起来更方便。

js
export function useHttp<T extends any>(api: (params?: any) => Promise<BaseResponseBody<T>>, options?: UseHttpOptions<T>): UseHttpResult<T> {
  const state = reactive<HttpState<T>>({
    data: (options?.initialValue ?? []) as any,
    loading: !options?.manual,
    error: null,
  });

  async function reload(params?: any) {
    state.error = null;
    state.loading = true;
    try {
      const { code, data, msg } = await api(params);
      if (isSuccess(code)) {
        // @ts-ignore
        state.data = data;
      } else {
        state.error = { code, data, msg };
      }
    } catch (e) {
      //
    } finally {
      state.loading = false;
    }
  }

  !options?.manual && reload();

  return {
    ...toRefs(state),
    // @ts-ignore
    state,
    reload,
  };
}

分页钩子,封装一些分页请求重复的逻辑。

js
export function usePaging<T>(
  api: (params: any) => any,
  options?: {
    manual?: boolean;
    pageSize?: number;
    transformData?: (data: T) => T[];
  }
): PagingRes<T> {
  // @ts-ignore
  const paging = reactive({
    refreshing: options?.manual ?? true,
    isLoadMore: false,
    noMore: false,
    dataSource: [], // 数据
    pages: {
      current: 1,
      size: options?.pageSize ?? 8,
    },
    isEmpty: computed(() => !paging.refreshing && paging.dataSource.length <= 0),
    code: 0,
    total: 0
  });

  function loadMore() {
    if (!paging.refreshing && !paging.isLoadMore && !paging.noMore) {
      loadData({ current: paging.pages.current + 1 }).catch(catchEmpty);
    }
  }

  async function onRefresh(cb?: Function) {
    try {
      await loadData({ current: 1 });
      // 接口成功回调
      cb?.();
    } catch (e) {
      //
    }
  }

  async function loadData(params: { current: number }) {
    paging.refreshing = params.current === 1;
    paging.isLoadMore = params.current > 1;
    try {
      const { code, data = {}, msg } = await api({ ...paging.pages, current: params.current });
      paging.code = code;
      if (isSuccess(code)) {
        if (data) {
          const { records = [], current, pages, size, total } = data;
          if (current === 1) {
            paging.dataSource = (options?.transformData ? options.transformData(records) : records) ?? [];
          } else {
            // 更多
            paging.dataSource = paging.dataSource.concat((options?.transformData ? options.transformData(records) : records) ?? []);
          }
          paging.total = total
          paging.pages.current = current;
          paging.noMore = current >= pages;
        }
      } else {
        //  就应该从 http 请求层面去控制是否要显示错误提示,因此要屏蔽这里
        // errorModal(msg);
      }
    } catch (e) {
      //
    } finally {
      paging.isLoadMore = false;
      paging.refreshing = false;
    }
  }

  !options?.manual && onRefresh();

  return { paging, loadMore, onRefresh };
}

批量请求,可以以对象或疏忽格式

js
export async function requestEvery(collection: Array<any> | Object) {
  if(Array.isArray(collection)) {
    return await Promise.all(collection)
  } else {
    const valueRes = await Promise.all(Object.values(collection))
    const keys = Object.keys(collection)
    return Object.fromEntries(keys.map((item, index) => {
      return [item, valueRes[index]]
    }))
  }
}

反馈请求,在请求过程中显示 loading 模态框。

js
export const callWithFeedback = async (fn: () => Promise<void>, msg?: string) => {
  await wx.showToast({
    title: '',
    icon: 'loading',
  });
  try {
    await fn();
    if (msg) {
      await wx.showToast({
        title: msg,
        icon: 'success',
      });
    }
  } catch (e: any) {
    await wx.showModal({
      content: stringOf(e.message ?? e.msg ?? e),
      showCancel: false,
    });
  } finally {
    await wx.hideToast();
  }
};

弹框组件的三种实现方式

第一种是将控制组件显示与隐藏的变量放在组件内部,通过父组件调用 $ref.model.show() 来显示弹框。

第二种是将显示与隐藏变量放在父组件内,通过 prop 传递给子组件。

第三种还是将变量放在父组件内,但是通过 v-if 来控制子组件的显示隐藏。

区别

第一、二种情况下,子组件的生命周期的创建与销毁,与其父组件保持同步。

第三种情况下,当子组件切换显示状态时,它的生命周期也会相应地销毁与重建。重建时变量也会被置为初始值。

vue 组件中自定义选项

js
	// 自定义选项
	// script 中通过 this.$options.customOptions 取值
	// template 中通过 $options.customOptions 取值
	customOptions: {
		keepAliveInclude
	}

Released under the MIT License.