TypeScript中括号属性访问与Pick工具类型的差异解析
核心问题:Pick工具类型与索引访问类型的差异
问题场景
定义如下CameraProps类型:
export type CameraProps = Omit<React.HTMLProps<HTMLVideoElement>, "ref"> & { audio?: boolean; audioConstraints?: MediaStreamConstraints["audio"]; mirrored?: boolean; screenshotFormat?: "image/webp" | "image/png" | "image/jpeg"; // between 0 & 1 screenshotQuality?: number; videoConstraints?: MediaStreamConstraints["video"]; fullScreenRecord?: boolean; screenshotDimensions?: ScreenshotDimensions; setImageSrc?: Dispatch<SetStateAction<string>>; overlay?: boolean; }
尝试用Pick定义类成员变量时:
private videoConstraints: Pick<CameraProps, 'videoConstraints'>;
执行赋值this.videoConstraints = videoConstraints;时触发类型错误:
Type boolean | MediaTrackConstraints | undefined is not assignable to type Pick<CameraProps, "videoConstraints">
Type undefined is not assignable to type Pick<CameraProps, "videoConstraints">
改用索引访问定义变量则完全正常:
private videoConstraints: CameraProps['videoConstraints'];
差异解析
1. Pick工具类型的作用
Pick<T, K>是TypeScript的工具类型,返回一个新的对象类型,该对象仅包含原类型T中指定的属性K。
针对本例:
Pick<CameraProps, 'videoConstraints'>
等价于:
{ videoConstraints?: MediaStreamConstraints["video"] }
这是一个包含可选属性videoConstraints的对象类型,而非单个属性的值类型。
2. 索引访问类型的作用
T[K]是索引访问类型,直接获取原类型T中属性K的具体类型,包括可选属性自带的undefined。
针对本例:
CameraProps['videoConstraints']
等价于:
MediaStreamConstraints["video"] | undefined
这是单个属性的值类型,完全匹配构造函数中传入的videoConstraints参数类型。
3. 错误原因
用Pick定义的变量是对象类型,但赋值的是单个属性的值(可能是undefined或MediaTrackConstraints等),两者类型不兼容,因此触发类型错误。而索引访问直接取到属性的值类型,赋值自然匹配。
完整代码参考
import React, {Dispatch, SetStateAction, MutableRefObject} from "react"; export type ScreenshotDimensions = { width: number; height: number; } export type CameraProps = Omit<React.HTMLProps<HTMLVideoElement>, "ref"> & { audio?: boolean; audioConstraints?: MediaStreamConstraints["audio"]; mirrored?: boolean; screenshotFormat?: "image/webp" | "image/png" | "image/jpeg"; // between 0 & 1 screenshotQuality?: number; videoConstraints?: MediaStreamConstraints["video"]; fullScreenRecord?: boolean; screenshotDimensions?: ScreenshotDimensions; setImageSrc?: Dispatch<SetStateAction<string>>; overlay?: boolean; } export type Maybe<T> = NonNullable<T> | undefined; export type VideoStatus = 'play' | 'error' | 'wait'; class CameraManager { private setVideoStatus: Dispatch<SetStateAction<VideoStatus>>; private videoRef: MutableRefObject<HTMLVideoElement | null>; private audio: Maybe<boolean>; private videoConstraints: CameraProps['videoConstraints']; private audioConstraints: CameraProps['audioConstraints']; private fullScreenRecord: Maybe<boolean>; constructor({ audio, setVideoStatus, fullScreenRecord, videoConstraints, videoRef, audioConstraints }: Pick<CameraProps, 'audio' | 'fullScreenRecord' | 'videoConstraints' | 'audioConstraints'> & { setVideoStatus: Dispatch<SetStateAction<VideoStatus>>, videoRef: MutableRefObject<HTMLVideoElement | null> }) { this.setVideoStatus = setVideoStatus; this.videoRef = videoRef; this.fullScreenRecord = fullScreenRecord; this.videoConstraints = videoConstraints; this.audioConstraints = audioConstraints; this.audio = audio; this.requestUserMedia(); } private async requestUserMedia() { const portrait = window.matchMedia("(orientation: portrait)").matches; try { this.setVideoStatus('play'); const constraints: MediaStreamConstraints = { video: this.fullScreenRecord ? { aspectRatio: portrait ? document.documentElement.clientHeight / document.documentElement.clientWidth : document.documentElement.clientWidth / document.documentElement.clientHeight, ...(typeof this.videoConstraints === 'object' ? this.videoConstraints : undefined), } : typeof this.videoConstraints === 'object' ? { ...this.videoConstraints } : true, }; if (this.audio) { constraints.audio = this.audioConstraints || true; } const stream = await navigator.mediaDevices.getUserMedia(constraints); if (!this.videoRef.current) { return; } this.videoRef.current.srcObject = stream; return stream; } catch (e) { this.setVideoStatus('error'); if (e instanceof Error) { // Handle specific errors here } console.log(e); } } }
内容的提问来源于stack exchange,提问作者Ali Ehyaie
相关产品推荐
相关产品推荐

