vue3.0+ts

322 阅读2分钟

项目创建

vue -- version的版本要大于4.5.0

css库

安装最新版本@next

cnpm install bootstrap@next --save

interface 定义数据,props断言

子组件

ColumnList.vue

export interface ColumnProps {
  id: number;
  title: string;
  avatar?: string;
  description: string;
}

  props: {
    list: {
      type: Array as PropType<ColumnProps[]>,
      require: true,
    },
  },
  setup(props: any) {
    return {
      list,
    };
  },

父组件

<column-list :list='list'></column-list>
import ColumnList, { ColumnProps } from "./components/ColumnList.vue";
const testData: ColumnProps[] = [
  {
    id: 1,
    title: "test1的专栏",
    description: "这是的test1专栏,有一段非常有意思的简介,可以更新一下欧",
    avatar:
      "http://vue-maker.oss-cn-hangzhou.aliyuncs.com/vue-marker/5ee22dd58b3c4520912b9470.jpg?x-oss-process=image/resize,m_pad,h_100,w_100",
  },
  {
    id: 2,
    title: "test2的专栏",
    description: "这是的test2专栏,有一段非常有意思的简介,可以更新一下欧",
    avatar:
      "http://vue-maker.oss-cn-hangzhou.aliyuncs.com/vue-marker/5ee22dd58b3c4520912b9470.jpg?x-oss-process=image/resize,m_pad,h_100,w_100",
  },
];
  setup() {
    return {
      list: testData,
    };
  },

点击下拉框外面,隐藏下拉框

原始写法

  <div class="dropdown"
       ref="dropdownRef">
  </div>
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
  setup() {
    const dropdownRef = ref<null | HTMLElement>(null);
    const handler = (e: MouseEvent) => {
      console.log("dropdownRef.value", dropdownRef.value);
      console.log("e.target", e.target);
      if (dropdownRef.value) {
        if (
          !dropdownRef.value.contains(e.target as HTMLElement) &&
          isOpen.value
        ) {
          isOpen.value = false;
        }
      }
    };
    onMounted(() => {
      document.addEventListener("click", handler);
    });
    onUnmounted(() => {
      document.removeEventListener("click", handler);
    });
    return {
      dropdownRef,  //返回和 ref 同名的响应式对象,就可以拿到对应的 dom 节点
    };
  },

dropdownRef.value是下拉框

e.target是鼠标点击的html

hook写法(自定义函数)

//hooks/useClickOutside.ts

import { ref, onMounted, onUnmounted, Ref } from 'vue'
const useClickOutside =(elementRef:Ref<null | HTMLElement>) => {
  const isClickOutside = ref(false)
  const handler = (e: MouseEvent) => {
    if (elementRef.value) {
      if (elementRef.value.contains(e.target as HTMLElement)) {
        isClickOutside.value = false
      } else {
        isClickOutside.value = true
      }
    }
  };
  onMounted(() => {
    document.addEventListener('click', handler)
  })
  onUnmounted(() => {
    document.removeEventListener('click', handler)
  })
  return isClickOutside
}
export default useClickOutside

父组件调用

import { watch } from "vue";
import useClickOutside from "../hooks/useClickOutside";
  setup() {
    const dropdownRef = ref<null | HTMLElement>(null);
    const isClickOutside = useClickOutside(dropdownRef);
    watch(isClickOutside, () => {
      if (isOpen.value && isClickOutside.value) {
        isOpen.value = false;
      }
    });
    const isOpen = ref(false);
    return {
      dropdownRef,
    };
  },

表单验证

父组件

      <validate-input :rules="emailRules"
                      placeholder="请输入邮箱地址"
                      type="text">
      </validate-input>
import ValidateInput, { RulesProp } from "./components/ValidateInput.vue";

const emailRules: RulesProp = [
  { type: "required", message: "电子邮箱地址不能为空" },
  { type: "email", message: "请输入正确的电子邮箱格式" },
];

子组件

<template>
  <div class="validate-input-container pb-3">
    <input type="text"
           class="form-control"
           :class="{'is-invalid': inputRef.error}"
           :value='inputRef.val'
           @blur="validateInput"
           @input="updateValue"
           v-bind="$attrs">
    <span v-if="inputRef.error"
          class="invalid-feedback">{{inputRef.message}}</span>
  </div>
</template>

<script lang="ts">
/* eslint-disable */
import { defineComponent, reactive, PropType } from "vue";
const emailReg = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
interface RuleProp {  //接口
  type: "required" | "email";
  message: string;
}
export type RulesProp = RuleProp[];  //type
export default defineComponent({
  props: {
    rules: Array as PropType<RulesProp>,
    modelValue: String,
  },
  inheritAttrs: false, //其它属性
  setup(props, context) {
    const inputRef = reactive({
      val: props.modelValue || "",
      error: false,
      message: "",
    });
    const validateInput = () => {
      if (props.rules) {
        const allPassed = props.rules.every((rule) => {
          let passed = true;
          inputRef.message = rule.message;
          switch (rule.type) {
            case "required":
              passed = inputRef.val.trim() !== "";
              break;
            case "email":
              passed = emailReg.test(inputRef.val);
              break;
            default:
              break;
          }
          return passed;
        });
        inputRef.error = !allPassed;
      }
    };
    const updateValue = (e: KeyboardEvent) => {
      const targetValue = (e.target as HTMLInputElement).value;
      inputRef.val = targetValue;
      context.emit("update:modelValue", targetValue);
    };
    return {
      inputRef,
      validateInput,
      updateValue,
    };
  },
});
</script>

jwt

jwt流程

声明一个方法,同时调用2个异步方法

Teleport

为什么我们需要 Teleport

Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”
场景:像 modals,toast 等这样的元素,很多情况下,我们将它完全的和我们的 Vue 应用的 DOM 完全剥离,管理起来反而会方便容易很多
原因在于如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难
另外,像 modals,toast 等这样的元素需要使用到 Vue 组件的状态(data 或者 props)的值
这就是 Teleport 派上用场的地方。我们可以在组件的逻辑位置写模板代码,这意味着我们可以使用组件的 data 或 props。然后在 Vue 应用的范围之外渲染它

api

拥有和components一样的prop,emit,slot,只不过多了一个to="#app",代表插入到id='app'的节点下

demo

teleport

// model.vue

<template>
  <teleport to="#app">
    <div class="model" v-if="isOpen">
      <h2><slot>this is a modal</slot></h2>
      <button @click="handleClose">Close</button>
    </div>
  </teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  props: {
    isOpen: Boolean
  },
  setup (props, context) {
    const handleClose = () => {
      context.emit('emitTeleportModelClose')
    }
    return {
      handleClose
    }
  }
})
</script>
<style>
.model {
  width: 200px;
  height: 200px;
  border: 2px solid black;
  background: white;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>

调用teleport

<template>
  <div>
    <button @click="openModal">Open Modal</button><br />
    <TeleportModel
      :isOpen="modalIsOpen"
      @emitTeleportModelClose="onTeleportModelClose"
    >
      My Modal !!!!</TeleportModel
    >
  </div>
 </template>
<script lang="ts">
  import TeleportModel from '@/components/teleport/model.vue'
  export default defineComponent({
  components: {
    TeleportModel
  },

  setup (props) {
    const modalIsOpen = ref(false)
    const openModal = () => {
      modalIsOpen.value = true
    }
    const onTeleportModelClose = () => {
      modalIsOpen.value = false
    }
    return {
      modalIsOpen,
      openModal,
    }
  }
</script>

image.png

this.$el、reactive refs、template refs

2.x可以在组件挂载之后通过this.el访问组件根元素3.x去掉this,并且支持Fragment,所以this.el访问组件根元素 3.x去掉this,并且支持Fragment,所以this.el没有存在的意义,建议通过refs访问DOM 当使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup() 中声明一个 ref 并返回它

使用reactive refs和template refs

<template>
  <div ref="root"></div>
</template>

<script>
  import { ref, onMounted, getCurrentInstance } from 'vue'

  export default {
    setup() {
      const vm = getCurrentInstance()
      const root = ref(null)

      onMounted(() => {
        // 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
        console.log(root.value) // <div/>
        console.log(vm.refs.root) // <div/>
        console.log(root.value === vm.refs.root) // true
      })

      return {
        root
      }
    }
  }
</script>

在v-for中使用

<template>
  <div v-for="(item, i) in list" :key="i" :ref="el => { divs[i] = el }">
    {{ item }}
  </div>
</template>

<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'

  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])

      // 确保在每次变更之前重置引用
      onBeforeUpdate(() => {
        divs.value = []
      })

      return {
        list,
        divs
      }
    }
  }
</script>

获取模板内的属性和方法

        <div ref="Upload"></div>
          setup () {
            const Upload: any = ref(null)
            return {
               Upload
            }
          }
Upload.value.属性
Upload.value.方法

hooks

下拉框展示隐藏,显示时,点击页面空白处,隐藏下拉框

hooks

import { ref, onMounted, onUnmounted, Ref } from 'vue'

const useClickOutside = (elementRef: Ref<null | HTMLElement>) => {
  const isClickOutside = ref(false)
  const handler = (e: MouseEvent) => {
    if (elementRef.value) {
      console.log('aa')

      if (elementRef.value.contains(e.target as HTMLElement)) {
        console.log('bb')
        isClickOutside.value = false
      } else {
        console.log('cc')
        isClickOutside.value = true
      }
    } else {
      console.log('dd')
    }
  }
  onMounted(() => {
    document.addEventListener('click', handler)
  })
  onUnmounted(() => {
    document.removeEventListener('click', handler)
  })
  return isClickOutside
}

export default useClickOutside

deopdown

<template>
  <div ref="dropdownRef" class="dropdown-container">
    <div @click.prevent="toggleOpen" class="container-show">
      <i v-if="isOpen" class="el-icon-caret-top"></i>
      <i v-else class="el-icon-caret-bottom"></i>
    </div>
    <div class="container-dialog" v-if="isOpen">
      <slot></slot>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
import useClickOutside from '@/hooks/useClickOutside'
export default defineComponent({
  props: {},
  setup () {
    const isOpen = ref<boolean>(false)
    const dropdownRef = ref<null | HTMLElement>(null)
    const toggleOpen = () => {
      isOpen.value = !isOpen.value
    }
    const isClickOutside = useClickOutside(dropdownRef)
    watch(isClickOutside, () => {
      console.log('33')

      if (isOpen.value && isClickOutside.value) {
        console.log('11')
        isOpen.value = false
        isClickOutside.value = false
      } else {
        isClickOutside.value = true
        console.log('22')
      }
    })
    return {
      isOpen,
      toggleOpen,
      dropdownRef
    }
  }
})
</script>
<style lang="scss" scoped>
.dropdown-container {
  position: relative;
  .container-show {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 50px;
    height: 50px;
  }
  .container-dialog {
    position: absolute;
    right: 0;
    top: 50px;
    z-index: 999;
  }
}
</style>

组件调用

        <Dropdown>
          <div class="dropdown-ul">
         
          </div>
        </Dropdown>

slot

父组件

      <Foo>
        <template v-slot:header>
          header1
        </template>
        <template v-slot:default>
          main1
        </template>
        <template v-slot:footer>
          header1
        </template>
      </Foo>

      <Foo>
        <template #header>
          header2
        </template>
        <template #default>
          main2
        </template>
        <template #footer>
          header2
        </template>
      </Foo>

子组件

  <div>
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>

ele表单 自定义校验规则

  setup (props, context) {
    const ruleFormsss = ref(null)
    const forceVersionCode = (rule: any, value: any, callback: any) => {
      const isForce = state.ruleForm.isForce.toString()
      console.log('isForce', isForce)

      if (isForce === '1') {
        if (value === '') {
          callback(new Error('请输入最小版本'))
        } else {
          callback()
        }
      } else {
        callback()
      }
    }
    const state = reactive({
      ruleForm: {
        versionCode: '',
        forceVersionCode: ''
      },
      rules: {
        versionCode: [
          { required: true, message: '请输入版本号', trigger: 'blur' }
        ],
        forceVersionCode: [
          { required: true, validator: forceVersionCode, trigger: 'blur' }
        ]
      },
      }