You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 13:31:05