一、实现组件继承属性类型
- 实现类型 ComponentType 以及函数createComponent的类型定义(无需实现功能)
- 使得函数createComponent能够创建一个React组件,支持设置三个属性值:props属性,emits事件以及inherit继承组件,具体要求看使用代码;
- 先做的简单一点,组件所有属性都是可选属性(props,emits以及继承的属性都是可选的)。
- 提示:先完整看一遍题目再开始实现功能;
function createComponent<Option extends ComponentOption>(option: Option): { (props: ComponentType<Option>): any } {return {} as any}// 基于button标签封装的组件,覆盖title属性以及onClick事件类型const Button = createComponent({inherit: "button", // 继承button标签所有属性以及事件props: {// 基础类型的属性label: String,width: Number,loading: Boolean,block: [Boolean, Number], // 联合属性类型:block: boolean|numbertitle: Number, // 覆盖button的属性类型 title:string -> title:number},emits: {'show-change': (len: number) => {}, // 自定义的事件类型click: (name: string) => {}, // 覆盖button的click事件类型},})console.log(/** 要求:* 1. 属性类型为 {label?:string, width?:number, loading?: boolean, block?:boolean|number, title?:number}* 2. 事件类型为:{onShowChange?:(len:number)=>void, onClick?:(name:string)=>void}* 3. 能够继承button的所有属性以及事件*/<Buttonlabel={""}width={100}title={111}onShowChange={len => {console.log(len.toFixed(0)) // 不允许有隐式的any类型,这里即使没有定义len的类型,len也应该能够自动推断出来为number类型}}onClick={e => {console.log(e.charAt(0))}}/>)// 基于Button组件封装的组件,覆盖label属性以及show-change,click事件类型const ProButton = createComponent({inherit: Button, // 继承Button所有属性以及事件props: {// 基础类型数据推断proLabel: String,label: [String, Number], // 覆盖Button的label属性类型:label:string -> label:string|number},emits: {'show-change': (el: HTMLButtonElement) => {},// 覆盖的事件类型click: (el: HTMLButtonElement) => {}, // 覆盖的事件类型'make-pro': () => {}, // 自定义事件类型},})console.log(/** 要求:* 1. 属性类型为 {proLabel?:string, label?:string|number}* 2. 事件类型为:{onShowChange?:(el: HTMLButtonElement)=>void, onClick?:(el: HTMLButtonElement)=>void, onMakePro?:()=>void}* 3. 继承Button组件所有的属性以及事件*/<ProButtonlabel={111}onShowChange={e => {console.log(e.offsetWidth) // 不允许有隐式的any类型,这里即使没有定义len的类型,len也应该能够自动推断出来为number类型}}onClick={e => {console.log(e.offsetWidth)}}onMakePro={() => {}}/>)
提示,如何得到button标签的属性类型
- 在文件:node_modules/@types/react/index.d.ts 中寻找 JSX.IntrinsicElements
- 比如div标签的属性类型为 JSX.IntrinsicElements[“div”]
const MyDiv = (props: JSX.IntrinsicElements["div"]) => nullconsole.log(<><div contentEditable={true} aria-label="div text"/><MyDiv contentEditable={true} aria-label="div text"/></>)
实现:
//横杠命名转化为驼峰命名type CapitalizeString<T> = T extends `${infer L}${infer R}` ? `${Uppercase<L>}${R}` : T;type CamelCase<T extends string, S extends string = ''> = T extends `${infer L}-${infer R}` ? CamelCase<R, `${S}${CapitalizeString<L>}`> : `${S}${CapitalizeString<T>}`interface SimpleConstruct { new(): any }; //表示class类型约束。例如:String Number ...//如果 T 是函数,返回函数的返回值;如果是类,返回类的实例本身;否则直接返回 T。此处是为了解决StringConstructor的问题type InferInstance<T> = T extends () => infer R ? R : (T extends new (...args: any[]) => infer R ? R : T);// type _string = StringConstructor extends new (...args: any[]) => infer R ? R: never; //Stringtype ComponentOption = {inherit?: keyof JSX.IntrinsicElements | ((props: any) => any);props?: Record<string, SimpleConstruct | SimpleConstruct[]>;emits?: Record<string, (...args: any[]) => any>;}//inherit可能继承button等原生标签,也可能是自定义封装的组件Buttontype ExtractInheritType<T> = T extends (props: infer R) => any ? R : (T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : {});type ExtractPropType<T> = { [k in keyof T]?: T[k] extends any[] ? InferInstance<T[k][number]> : InferInstance<T[k]> }; //将类型数组转成联合类型//将绑定的事件处理成标准格式type ExtractEmitType<T> = {[k in keyof T as `on${k extends string ? CamelCase<k> : ''}`]?: T[k] extends (...args: infer A) => any ? (...args: A) => void : T[k]};type MergeTypes<Inherit, Props, Emits> = Props & Emits & Omit<Inherit, keyof Props | keyof Emits>; //合并type ComponentType<Option> = Option extends { inherit?: infer Inherit, props?: infer Props, emits?: infer Emits } ?MergeTypes<ExtractInheritType<Inherit>, ExtractPropType<Props>, ExtractEmitType<Emits>> : never;function createComponent<Option extends ComponentOption>(option: Option): { (props: ComponentType<Option>): any } { return {} as any };
二、(React)实现Hook函数useAsyncMethods
- React同学专属题目,Vue同学请看第三题;
- 使用hook函数以及hook组件实现,如果可以的话尽量不要使用class组件;
- useAsyncMethods函数是一个hook函数,接收两个参数:(methods:Record
,alone?:boolean) - SimpleMethod类型
interface SimpleMethod {(...args: any[]): any} - 返回值类型为一个对象,这个对象的类型与参数methods一致,不过会多出来一个属性 loading;loading是一个对象,对象的key类型为methods中的key,除此之外还多了一个key,叫做global,loading对象所有属性值类型都是布尔值;这些属性的作用如下所示:
- 比如
const methods = useAsyncMethods({fun1:(val:string)=>{},fun2:(val:number)=>{}}) methods.fun1与定义的时候的类型一致,只不过返回值一定是Promise的包装类型,不管原始的fun1是否为异步函数;methods.fun2也是一样,与定义的时候的类型一致;methods.loading.fun1可以用来判断 fun1是否执行完毕,同理methods.loading.fun2也是;methods.loading.global任意一个函数没有执行完,这个值就是true,所有函数执行完毕之后,这个值就是false;
- 比如
- 当
methods.fun1没有执行完毕时,再次调用该函数无效,也就是在没有结束之前不会执行定义的时候的fun1函数; - 当设置了alone参数为true的时候,只有当所有函数执行完毕之后才能执行下一个函数;也就是说,alone为false的时候,函数执行只跟自己是互斥的,fun1执行完之后才能再次执行fun1; 与fun2无关;当设置了alone为true的时候,所有函数都是互斥的,fun1执行完之后才能执行fun1,fun2;
示例效果页面
- http://martsforever-demo.gitee.io/template-plain-react-micro-base/
- 子应用 -> React子应用 -> 测试 createAsyncMethods 按钮
- 目前有四个按钮,每个按钮对应一个异步函数执行;
- 每个异步函数都会有一个state,是个数字类型的count,异步函数执行完之后count会加一;
用来测试的示例代码
import React, {useState} from "react";import {randomDelay, useAsyncMethods} from "@/pages/message/useAsyncMethods";import {Button, Spin} from "antd";const Demo1 = () => {const [method1, setMethod1] = useState(0)const [method2, setMethod2] = useState(0)const [method3, setMethod3] = useState(0)const [togetherMethod2and3, setTogetherMethod2and3] = useState(0)const methods = useAsyncMethods({method1: async (id: string) => {console.log('任务一开始')await randomDelay(1000, 3000)console.log('任务一结束')setMethod1(val => val + 1)},method2: async (start: number, end: number) => {console.log('任务二开始')await randomDelay(1000, 2000)console.log('任务二结束')setMethod2(val => val + 1)return start + end},method3: async (result: any) => {console.log('任务三开始', {result})await randomDelay(2000, 3000)console.log('任务三结束')setMethod3(val => val + 1)},togetherMethod2and3: async () => {console.log('任务四开始')// const ret = await methods.method2() // 错误,缺少必须参数start以及endconst ret = await methods.method2(2, 3)// await methods.method3(ret.charAt(0)) // 错误,返回值类型为数字await methods.method3(ret.toFixed(2))console.log('任务四结束')setTogetherMethod2and3(val => val + 1)},})return <><div style={{backgroundColor: 'white', padding: '20px'}}><h1>测试createAsyncMethods</h1><h3>允许多个不同的异步同时执行,但是同一个异步函数不能同时执行多个,必须在函数执行完毕之后,才能开始再次执行该异步函数</h3><Button.Group><Button onClick={() => methods.method1('__')}><span>一号异步任务({method1})</span>{!!methods.loading.method1 && <Spin/>}</Button><Button onClick={() => methods.method2(0, 1)}><span>二号异步任务({method2})</span>{!!methods.loading.method2 && <Spin/>}</Button><Button onClick={() => methods.method3('?')}><span>三号异步任务({method3})</span>{!!methods.loading.method3 && <Spin/>}</Button><Button onClick={() => methods.togetherMethod2and3()}><span>四号异步任务({togetherMethod2and3})</span>{!!methods.loading.togetherMethod2and3 && <Spin/>}</Button></Button.Group></div></>}const Demo2 = () => {const [method1, setMethod1] = useState(0)const [method2, setMethod2] = useState(0)const [method3, setMethod3] = useState(0)const [togetherMethod2and3, setTogetherMethod2and3] = useState(0)const methods = useAsyncMethods((() => {const m = {method1: async (id: string) => {console.log('任务一开始')await randomDelay(1000, 3000)console.log('任务一结束')setMethod1(val => val + 1)},method2: async (start: number, end: number) => {console.log('任务二开始')await randomDelay(1000, 2000)console.log('任务二结束')setMethod2(val => val + 1)return start + end},method3: async (result: any) => {console.log('任务三开始', {result})await randomDelay(2000, 3000)console.log('任务三结束')setMethod3(val => val + 1)},togetherMethod2and3: async () => {console.log('任务四开始')// const ret = await methods.method2() // 错误,缺少必须参数start以及endconst ret = await m.method2(2, 3)// await methods.method3(ret.charAt(0)) // 错误,返回值类型为数字await m.method3(ret.toFixed(2))console.log('任务四结束')setTogetherMethod2and3(val => val + 1)},}return m})(), true)return <><div style={{backgroundColor: 'white', padding: '20px'}}><h3>无论是否为同一个异步函数,同一时刻仅能够有一个异步函数在执行</h3><Button.Group><Button onClick={() => methods.method1('__')}><span>一号异步任务({method1})</span>{!!methods.loading.method1 && <Spin/>}</Button><Button onClick={() => methods.method2(0, 1)}><span>二号异步任务({method2})</span>{!!methods.loading.method2 && <Spin/>}</Button><Button onClick={() => methods.method3('?')}><span>三号异步任务({method3})</span>{!!methods.loading.method3 && <Spin/>}</Button><Button onClick={() => methods.togetherMethod2and3()}><span>四号异步任务({togetherMethod2and3})</span>{!!methods.loading.togetherMethod2and3 && <Spin/>}</Button></Button.Group></div></>}export default () => {return <><Demo1/><Demo2/></>}
问题
Demo2中的useAsyncMethods为什么要这样创建;
Demo2设置了全局一次只能有一个异步任务执行,为了避免在执行任务四时,其它异步任务无法调用的情况。所以需要调用原生的方法。
实现:
import React, { useState } from "react";//randomDelay的实现const delay = (t = 0) => new Promise(resolve => setTimeout(resolve, t));export const randomDelay = async (start: number, end: number) => await delay(Math.random() * (end - start) + start);interface SimpleMethod { (...args: any[]): any };const getAsyncState = <T>(setter: (getter: (val: T) => any) => any): Promise<T> => {return new Promise<T>(resolve => setter(val => {resolve(val);return val;}))}export function useAsyncMethods<Methods extends Record<string, SimpleMethod>>(methods: Methods, alone?: boolean) {const methodNames = Object.keys(methods) as (keyof Methods)[];const newMethods = {} as Record<keyof Methods, SimpleMethod>;const loadingInitialState = {} as Record<keyof Methods, boolean>;methodNames.forEach(methodName => {const method = methods[methodName];loadingInitialState[methodName] = false;newMethods[methodName] = async (...args: any[]) => {const loading = await getAsyncState(setLoading);if (alone) {if (loading.global) { return }} else {if (loading[methodName]) { return }}setLoading(loading => {return {...loading,[methodName]: true,global: true}});try {return await method(...args);} finally {setLoading(loading => {return {...loading,[methodName]: false,global: methodNames.findIndex(name => name !== methodName && loading[name]) > -1}})}}});const [loading, setLoading] = useState({...loadingInitialState,global: false,})return {...newMethods,loading}}
三、(Vue3.0)实现Composition函数createAsyncMethods
- Vue3.0同学专属题目,React同学请看第二题
- 使用reactive api实现
- createAsyncMethods函数是一个普通函数,接收两个参数:(methods:Record
,alone?:boolean) - SimpleMethod类型
interface SimpleMethod {(...args: any[]): any} - 返回值类型为一个对象,这个对象的类型与参数methods一致,不过会多出来一个属性 loading;loading是一个对象,对象的key类型为methods中的key,除此之外还多了一个key,叫做global,loading对象所有属性值类型都是布尔值;这些属性的作用如下所示:
- 比如
const methods = createAsyncMethods({fun1:(val:string)=>{},fun2:(val:number)=>{}}) methods.fun1与定义的时候的类型一致,只不过返回值一定是Promise的包装类型,不管原始的fun1是否为异步函数;methods.fun2也是一样,与定义的时候的类型一致;methods.loading.fun1可以用来判断 fun1是否执行完毕,同理methods.loading.fun2也是;methods.loading.global任意一个函数没有执行完,这个值就是true,所有函数执行完毕之后,这个值就是false;
- 比如
- 当
methods.fun1没有执行完毕时,再次调用该函数无效,也就是在没有结束之前不会执行定义的时候的fun1函数; - 当设置了alone参数为true的时候,只有当所有函数执行完毕之后才能执行下一个函数;也就是说,alone为false的时候,函数执行只跟自己是互斥的,fun1执行完之后才能再次执行fun1; 与fun2无关;当设置了alone为true的时候,所有函数都是互斥的,fun1执行完之后才能执行fun1,fun2;
示例效果页面
- http://martsforever-demo.gitee.io/template-plain-react-micro-base
- 子应用 -> Vue子应用 -> 测试 createAsyncMethods 按钮
- 目前有四个按钮,每个按钮对应一个异步函数执行;
- 每个异步函数都会有一个state,是个数字类型的count,异步函数执行完之后count会加一;
用来测试的示例代码
<template><div style="background-color: white;padding: 20px 10px"><h1>测试createAsyncMethods</h1><h3>允许多个不同的异步同时执行,但是同一个异步函数不能同时执行多个,必须在函数执行完毕之后,才能开始再次执行该异步函数</h3><el-button @click="methods.method1"><span>一号异步任务({{ state.method1 }})</span><el-icon class="is-loading" v-if="methods.loading.method1"><Loading/></el-icon></el-button><el-button @click="methods.method2"><span>二号异步任务({{ state.method2 }})</span><el-icon class="is-loading" v-if="methods.loading.method2"><Loading/></el-icon></el-button><el-button @click="methods.method3"><span>三号异步任务({{ state.method3 }})</span><el-icon class="is-loading" v-if="methods.loading.method3"><Loading/></el-icon></el-button><el-button @click="methods.togetherMethod2and3"><span>四号异步任务({{ state.togetherMethod2and3 }})</span><el-icon class="is-loading" v-if="methods.loading.togetherMethod2and3"><Loading/></el-icon></el-button><h3>无论是否为同一个异步函数,同一时刻仅能够有一个异步函数在执行</h3><el-button @click="methods2.method1"><span>一号异步任务({{ state2.method1 }})</span><el-icon class="is-loading" v-if="methods2.loading.method1"><Loading/></el-icon></el-button><el-button @click="methods2.method2"><span>二号异步任务({{ state2.method2 }})</span><el-icon class="is-loading" v-if="methods2.loading.method2"><Loading/></el-icon></el-button><el-button @click="methods2.method3"><span>三号异步任务({{ state2.method3 }})</span><el-icon class="is-loading" v-if="methods2.loading.method3"><Loading/></el-icon></el-button><el-button @click="methods2.togetherMethod2and3"><span>四号异步任务({{ state2.togetherMethod2and3 }})</span><el-icon class="is-loading" v-if="methods2.loading.togetherMethod2and3"><Loading/></el-icon></el-button></div></template><script lang="ts">import {createAsyncMethods, randomDelay} from "@/pages/message/createAsyncMethods";import {Loading} from '@element-plus/icons'import {defineComponent, reactive} from 'vue'export default defineComponent({components: {Loading},setup() {const state = reactive({method1: 0,method2: 0,method3: 0,togetherMethod2and3: 0,})const methods = createAsyncMethods({method1: async (id: string) => {console.log('任务一开始')await randomDelay(1000, 3000)console.log('任务一结束')state.method1++},method2: async (start: number, end: number) => {console.log('任务二开始')await randomDelay(1000, 2000)console.log('任务二结束')state.method2++return start + end},method3: async (result: any) => {console.log('任务三开始', {result})await randomDelay(2000, 3000)console.log('任务三结束')state.method3++},togetherMethod2and3: async () => {console.log('任务四开始')// const ret = await methods.method2() // 错误,缺少必须参数start以及endconst ret = await methods.method2(2, 3)// await methods.method3(ret.charAt(0)) // 错误,返回值类型为数字await methods.method3(ret.toFixed(2))console.log('任务四结束')state.togetherMethod2and3++},})const state2 = reactive({method1: 0,method2: 0,method3: 0,togetherMethod2and3: 0,})const methods2 = createAsyncMethods((() => {const m = {method1: async (id: string) => {console.log('任务一开始')await randomDelay(1000, 3000)console.log('任务一结束')state2.method1++},method2: async (start: number, end: number) => {console.log('任务二开始')await randomDelay(1000, 2000)console.log('任务二结束')state2.method2++return start + end},method3: async (result: any) => {console.log('任务三开始', {result})await randomDelay(2000, 3000)console.log('任务三结束')state2.method3++},togetherMethod2and3: async () => {console.log('任务四开始')const ret = await m.method2(2, 3)await m.method3(ret.toFixed(2))console.log('任务四结束')state2.togetherMethod2and3++},}return m})(), true)return {state,methods,state2,methods2,}},})</script>
问题
methods2中的createAsyncMethods为什么要这样创建;
四、(React)实现使用弹框选择数据服务:pick函数
- React同学专属题目,Vue同学请看第五题
- pick函数是一个异步函数,参数是一个对象;返回值的类型,依据参数类型而定;参数类型如下所示
interface iUsePickOption<Data> {data: Data[],render: (row: Data, index: number) => any}interface iUsePickOptionMultiple<Data> {data: Data[],render: (row: Data, index: number) => anymultiple: true}
- (单选)当参数符合
iUsePickOption时,返回值为选项data数组中元素的类型; - (多选)当参数符合
iUsePickOptionMultiple是,返回值类型等同于选项data,也是个数组; - 使用弹框渲染这个data数据,点击弹框取消按钮或者遮罩时,pick异步任务终止(Promise.reject);
- 用户没有选择数据点击弹框确定按钮的时候,如果没有选中任何一条数据,提示选择数据,但是不得关闭弹框;
- 用户选中数据,并且点击确定之后,异步任务执行完毕,返回用户选中数据;
- pick函数还能够接收一个泛型,当传入这个泛型的时候,选项中的data的类型将忽略,返回值以这个泛型类型为主,如下列示例代码中的第三个示例为例;
const pickWithCustomType = await pick<Student>(...)得到的返回值类型为Student,如果是多选,则返回值为Student[]
提示:函数pick的类型为一个重载函数
测试代码如下所示:
import {Button, Modal} from "antd"import {pick} from "@/pages/demo-pick/pick";import studentJsonData from './student.json'export default () => {interface Staff {name: string,age: number,avatar: string,}const staffData: Staff[] = [{"name": "张三","age": 20,"avatar": "http://abc.com/zhangsan"},{"name": "李四","age": 21,"avatar": "http://abc.com/lisi"},{"name": "王五","age": 22,"avatar": "http://abc.com/wangwu"}]/*---------------------------------------单选-------------------------------------------*/const pick1 = async () => {// pickPerson自动推导类型为 Staffconst pickStaff = await pick({data: staffData,// render函数的row参数自动推导为Person,与data选项的persons对象类型保持一致render: (row) => [row.name, row.age, row.avatar].join(',')})Modal.info({maskClosable: true, content: [pickStaff.name, pickStaff.age, pickStaff.avatar].join(',')})}/*---------------------------------------多选-------------------------------------------*/const pick2 = async () => {// pickPersonList自动推导类型为 Staff[]const pickStaffList = await pick({data: staffData,// render函数的row参数自动推导为Person,与data选项的persons对象类型保持一致render: (row) => [row.name, row.age, row.avatar].join(','),multiple: true,})Modal.info({maskClosable: true,content:pickStaffList.map(staff => [staff.name, staff.age, staff.avatar].join(',')).join('\n')})}/*---------------------------------------多选,手动传递类型-------------------------------------------*/interface Student {name: string,code: string,grade: number}const pick3 = async () => {const pickWithCustomType = await pick<Student>({// 无法确定data的类型data: studentJsonData,// render函数的row参数自动推导为Person,与data选项的persons对象类型保持一致render: (row) => [row.name, row.grade, row.code].join(','),multiple: true,})// pickWithCustomType推导类型为 Student[]Modal.info({maskClosable: true,content:pickWithCustomType.map(student => [student.name, student.grade, student.code].join(',')).join('\n')})}return (<div style={{backgroundColor: 'white', padding: '20px 10px'}}><h1>TestPick</h1><Button.Group><Button onClick={pick1}>选中单条数据</Button><Button onClick={pick2}>选中多条数据</Button><Button onClick={pick3}>选中多条数据(手动传递类型)</Button></Button.Group></div>)}
student.json
[{"name": "张三","grade": 4,"code": "01001"},{"name": "李四","grade": 5,"code": "01002"},{"name": "王五","grade": 6,"code": "01003"}]
实现:
import {Modal} from 'antd'import {useState} from "react";type Deferred<T> = {promise: Promise<T>;resolve: (value?: T) => void;reject: (reason?: any) => void;};const defer = function <T>() {let dfd = {} as Deferred<T>;dfd.promise = new Promise((resolve, reject) => {dfd.resolve = resolve;dfd.reject = reject;})return dfd}type SimpleObject = Record<string, any>type NotUnknown<T, K> = unknown extends T ? K : Tinterface iUsePickOption<Data> {data: Data[],render: (row: Data, index: number) => any}interface iUsePickOptionMultiple<Data> extends iUsePickOption<Data> {multiple: true}/*函数类型: 单选*/export function pick<T = unknown, ListT = SimpleObject>(option: iUsePickOption<NotUnknown<T, ListT>>): Promise<NotUnknown<T, ListT>>/*函数类型: 多选*/export function pick<T = unknown, ListT = SimpleObject>(option: iUsePickOptionMultiple<NotUnknown<T, ListT>>): Promise<NotUnknown<T, ListT>[]>/*函数实现*/export function pick<T = unknown, ListT = SimpleObject>(option: iUsePickOption<NotUnknown<T, ListT>> | iUsePickOptionMultiple<NotUnknown<T, ListT>>) {const dfd = defer<NotUnknown<T, ListT> | NotUnknown<T, ListT>[]>()// 用来在beforeClose的时候判断是否已经选中任何内容const state = {checked: null as null | NotUnknown<T, ListT>,checkedList: [] as NotUnknown<T, ListT>[],}const Message = () => {const [checked, setChecked] = useState(null as null | NotUnknown<T, ListT>)const [checkedList, setCheckedList] = useState([] as NotUnknown<T, ListT>[])/*获取元素位置索引*/const getIndex = (row: any) => checkedList.indexOf(row)/*元素是否已经选中*/const isChecked = (row: any) => 'multiple' in option && option.multiple ? getIndex(row) > -1 : checked == rowconst onClick = (row: NotUnknown<T, ListT>) => {if ('multiple' in option && option.multiple) {if (isChecked(row)) {checkedList.splice(getIndex(row), 1)} else {checkedList.push(row as any)}state.checkedList = checkedListsetCheckedList([...checkedList])} else {state.checked = rowsetChecked(row)}}return (<ul>{option.data.map((row, index) => (<listyle={{padding: '10px 16px',backgroundColor: isChecked(row) ? 'aliceblue' : ''}}onClick={() => onClick(row as any)}key={index}>{option.render(row as any, index)}</li>))}</ul>)}const modal = Modal.info({title: '选择',okText: '确定',cancelText: '取消',closable: true,maskClosable: true,content: <Message/>,onCancel: () => dfd.reject('cancel'),okButtonProps: {onClick: () => {if ('multiple' in option && option.multiple) {if (state.checkedList.length === 0) {return Modal.info({content: '请至少选中一条数据', maskClosable: true})} else {dfd.resolve(state.checkedList)}} else {if (!state.checked) {return Modal.info({content: '请选中一条数据', maskClosable: true})} else {dfd.resolve(state.checked)}}return modal.update({visible: false})},},})return dfd.promise}
五、(Vue3.0)实现使用弹框选择数据服务:pick函数
- Vue同学专属题目,React同学请看第四题
- pick函数是一个异步函数,参数是一个对象;返回值的类型,依据参数类型而定;参数类型如下所示
interface iUsePickOption<Data> {data: Data[],render: (row: Data, index: number) => any}interface iUsePickOptionMultiple<Data> {data: Data[],render: (row: Data, index: number) => anymultiple: true}
- (单选)当参数符合
iUsePickOption时,返回值为选项data数组中元素的类型; - (多选)当参数符合
iUsePickOptionMultiple是,返回值类型等同于选项data,也是个数组; - 使用弹框渲染这个data数据,点击弹框取消按钮或者遮罩时,pick异步任务终止(Promise.reject);
- 用户没有选择数据点击弹框确定按钮的时候,如果没有选中任何一条数据,提示选择数据,但是不得关闭弹框;
- 用户选中数据,并且点击确定之后,异步任务执行完毕,返回用户选中数据;
- pick函数还能够接收一个泛型,当传入这个泛型的时候,选项中的data的类型将忽略,返回值以这个泛型类型为主,如下列示例代码中的第三个示例为例;
const pickWithCustomType = await pick<Student>(...)得到的返回值类型为Student,如果是多选,则返回值为Student[]
提示:函数pick的类型为一个重载函数
测试代码如下所示:
<template><div style="background-color: white;padding: 20px 10px"><h1>TestPick</h1><el-button-group><el-button @click="pick1">选中单条数据</el-button><el-button @click="pick2">选中多条数据</el-button><el-button @click="pick3">选中多条数据(手动传递类型)</el-button></el-button-group></div></template><script lang="ts">import {pick} from "./pick";import studentJsonData from './student.json'import {ElMessageBox} from 'element-plus'export default {setup() {interface Staff {name: string,age: number,avatar: string,}const staffData: Staff[] = [{"name": "张三","age": 20,"avatar": "http://abc.com/zhangsan"},{"name": "李四","age": 21,"avatar": "http://abc.com/lisi"},{"name": "王五","age": 22,"avatar": "http://abc.com/wangwu"}]/*---------------------------------------单选-------------------------------------------*/const pick1 = async () => {// pickPerson自动推导类型为 Staffconst pickStaff = await pick({data: staffData,// render函数的row参数自动推导为Person,与data选项的persons对象类型保持一致render: (row) => [row.name, row.age, row.avatar].join(',')})ElMessageBox({message: [pickStaff.name, pickStaff.age, pickStaff.avatar].join(',')})}/*---------------------------------------多选-------------------------------------------*/const pick2 = async () => {// pickPersonList自动推导类型为 Staff[]const pickStaffList = await pick({data: staffData,// render函数的row参数自动推导为Person,与data选项的persons对象类型保持一致render: (row) => [row.name, row.age, row.avatar].join(','),multiple: true,})ElMessageBox({message:pickStaffList.map(staff => [staff.name, staff.age, staff.avatar].join(',')).join('\n')})}/*---------------------------------------多选,手动传递类型-------------------------------------------*/interface Student {name: string,code: string,grade: number}const pick3 = async () => {const pickWithCustomType = await pick<Student>({// 无法确定data的类型data: studentJsonData,// render函数的row参数自动推导为Person,与data选项的persons对象类型保持一致render: (row) => [row.name, row.grade, row.code].join(','),multiple: true,})// pickWithCustomType推导类型为 Student[]ElMessageBox({message:pickWithCustomType.map(student => [student.name, student.grade, student.code].join(',')).join('\n')})}return {pick1,pick2,pick3,}},}</script>
student.json
[{"name": "张三","grade": 4,"code": "01001"},{"name": "李四","grade": 5,"code": "01002"},{"name": "王五","grade": 6,"code": "01003"}]
