React + TypeScript 默认 Props 的处理

React 中的默认 Props

通过组件的 defaultProps 属性可为其 Props 指定默认值。

以下示例来自 React 官方文档 - Default Prop Values

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// Specifies the default values for props:
Greeting.defaultProps = {
  name: ‘Stranger‘
};

// Renders "Hello, Stranger":
ReactDOM.render(
  <Greeting />,
  document.getElementById(‘example‘)
);

如果编译过程使用了 Babel 的 transform-class-properties 插件,还可以这么写:

class Greeting extends React.Component {
  static defaultProps = {
    name: ‘stranger‘
  }

  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

加入 TypeScript

加入 TypeScript 后

interface Props {
  name?: string;
}

class Greeting extends React.Component<Props, {}> {
  static defaultProps = {
    name: "stranger",
  };

  render() {
    return <div>Hello, {this.props.name}</div>;
  }
}

此时不支持直接通过类访问 defaultProps 来赋值以设置默认属性,因为 React.Component 类型上并没有该属性。

// ??Property ‘defualtProps‘ does not exist on type ‘typeof Greeting‘.ts(2339)
Greeting.defualtProps = {
  name: "stranger",
};

默认属性的类型

上面虽然实现了通过 defaultProps 来指定属性的默认值,但 defaultProps 的类型是不受约束的,和 Props 没有关联上。以至于我们可以在 defaultProps 里面放任何值,显然这是不科学的。

class Greeting extends React.Component<Props, {}> {
  static defaultProps = {
    name: "stranger",
    // 并不会报错
+    foo: 1,
+    bar: {},
  };
 // ...
}

同时对于同一字段,我们不得不书写两次代码。一次是定义组件的 Props,另一次是在 defaultProps 里。如果属性有增删或名称有变更,两个地方都需要改。

为了后面演示方便,现在给组件新增一个必填属性 age:number

interface Props {
  age: number;
  name?: string;
}

class Greeting extends React.Component<Props, {}> {
  static defaultProps = {
    name: "stranger",
  };

  render() {
    const { name, age } = this.props;
    return (
      <div>
        Hello, {name}, my age is {age}
      </div>
    );
  }
}

通过可选属性抽取出来,利用 typeof 获取其类型和必传属性结合来形成组件的 Props 可解决上面提到的两个问题。

所以优化后的代码成了:

const defaultProps = {
  name: "stranger",
};

type Props = {
  age: number;
} & Partial<typeof defaultProps>;

class Greeting extends React.Component<Props, {}> {
  static defaultProps = defaultProps;

  render() {
    const { name, age } = this.props;
    return (
      <div>
        Hello, {name}, my age is {age}
      </div>
    );
  }
}

注意我们的 Props 是通过和 typeof defaultProps 组合而形成的,可选属性中的 name 字段在整个代码中只书写了一次。

当我们更新了 defaultProps 时整个组件的 Props 也同步更新,所以 defaultProps 中的字段一定是组件所需要的字段。

默认值的判空检查优化

讲道理,如果属性提供了默认值,在使用时,可不再需要判空,因为其一定是有值的。但 TypeScript 在编译时并不知道,因为有默认值的属性是被定义成可选的 ?

比如我们尝试访问 name 属性的长度,

class Greeting extends React.Component<Props, {}> {
  static defaultProps = defaultProps;

  render() {
    const { name } = this.props;
    return (
      <div>
        {/* ??Object is possibly ‘undefined‘.ts(2532) */}
        name length is {name.length}
      </div>
    );
  }
}

因为此时我们的 Props 实际上是:

type Props = {
  age: number;
} & Partial<typeof defaultProps>;
// 相当于:
type Props = {
  age: number;
  name?: string;
};

修正方法有多个,最简单的是使用非空判定符/Non-null assertion operator

非空判定符

- name length is {name.length}
+ name length is {name!.length}

这意味着每一处使用的地方都需要做类似的操作,当程序复杂起来时不太可控。但多数情况下应付日常使用,这样已经够了。

类型转换

因为组件内部有默认值的保证,所以字段不可能为空,因此,可对组件内部使用非空的属性类型来定义组件,而对外仍暴露原来的版本。

const Greeting = class extends React.Component<
-  Props,
+  Props & typeof defaultProps,
  {}
> {
  static defaultProps = defaultProps;

  render() {
    const { name } = this.props;
    return (
      <div>
-        name length is {name!.length}
+        name length is {name.length}
      </div>
    );
  }
-};
+} as React.ComponentClass<Props>;

通过 as React.ComponentClass<Props> 的类型转换,对外使用 Greeting 时属性中 name 还是可选的,但组件内部实际使用的是 Props & typeof defaultProps,而不是 Partial<T> 版本的,所以规避了字段可能为空的报错。

通过高阶组件的方式封装默认属性的处理

通过定义一个高阶组件比如 withDefaultProps 将需要默认属性的组件包裹,将默认值的处理放到高阶组件中,同样可解决上述问题。

function withDefaultProps<P extends object, DP extends Partial<P>>(
  dp: DP,
  component: React.ComponentType<P>,
) {
  component.defaultProps = dp;
  type RequiredProps = Omit<P, keyof DP>;
  return (component as React.ComponentType<any>) as React.ComponentType<
    RequiredProps & DP
  >;
}

然后我们的组件则可以这样来写:

const defaultProps = {
  name: "stranger",
};

interface Props {
  name: string;
  age: number;
}

const _Greeting = class extends React.Component<Props, {}> {
  public render() {
    const { name } = this.props;
    return <div>name length is {name.length}</div>;
  }
};

export const Greeting = withDefaultProps(defaultProps, _Greeting);

这种方式就比较通用一些,将 withDefaultProps 抽取成一个公共组件,后续其他组件都可使用。但此种情况下就没有很好地利用已经定义好的默认值 defaultProps 中的字段,书写 Props 时还需要重复写一遍字段名。

相关资源

原文地址:https://www.cnblogs.com/Wayou/p/react_typescript_default_props.html

时间: 2024-07-30 05:58:28

React + TypeScript 默认 Props 的处理的相关文章

使用react搭建组件库(二):react+typescript

1 使用了react官方脚手架:create-react-app https://github.com/facebook/create-react-app npm run eject 可以打开配置文件 自定义配置文件 执行安装: npx create-react-app ts-with-react --typescript npx 只有在npm5.2以上版本才有 1.避免安装全局模块:临时命令,使用后删除,再次执行的时候再次下载 2.调用项目内部安装的模块用起来更方便:比如 在package.j

Webpack + React 开发 03 props

React中组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如 <HelloWorld name="John"> ,就是 HelloWorld 组件加入一个 name 属性,值为 John.和直接使用 <div name="John"> 不同,React组件被渲染出来之后,在渲染之后的dom节点中是不能直接看得到 name 属性的,怎么获取呢?组件的属性可以在组件类的 this.props 对象上获取,比如 name 属性就可

React + TypeScript:元素引用的传递

React 中需要操作元素时,可通过 findDOMNode() 或通过 createRef() 创建对元素的引用来实现.前者官方不推荐,所以这里讨论后者及其与 TypeScript 结合时如何工作. React 中的元素引用 正常的组件中,可通过创建对元素的引用来获取到某元素然后进行相应操作.比如元素加载后将焦点定位到输入框. class App extends Component { constructor(props){ super(props); this.inputRef = Reac

React+TypeScript

新建项目 新建工程文件夹 1 $ mkdir TypeScriptDemo && cd TypeScriptDemo 初始化工程 除了package name 其他都默认敲回车即可 1 2 3 4 $ npm init package name: (TypeScriptDemo) TypeScriptDemo ... Is this ok? (yes) yes 组织目录结构 src目录存放工程代码,dist最终由webpack生成 1 2 3 4 TypeScriptDemo/ ├─ di

react + typescript 学习

react,前端三大框架之一,也是非常受开发者追捧的一门技术.而 typescript 是 javascript 的超集,主要特点是对 类型 的检查.二者的结合必然是趋势,不,已经是趋势了.react 文档.typescript 文档都看过,例子也敲过了,对此也都有了一定的理解,但是把二者很好的结合在一起,还是遇到了一些问题.纯粹记录一些,当然也希望有优秀资源的,提供一下,分享一下.提前道谢了~ 学习曲线 首先,想到的是到 官网,看相关文档,会系统些. typescript 中文网 jsx ty

Node.js 之react.js组件-Props应用

render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码(个人理解:将组件进行函数化,通过调用组件名实现,组件的利用,以元素的形式调用,并渲染画面) 具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑. 具体实例(代码来自官网):URL:https://zh-hans.reactjs.org/docs/render-props.html#___gatsby 笔记:代码中实现的组件调用,是将一个组

React中constructor(props){}究竟是什么,以及super(props)与super()

定义class组件,为什么需要加上 super() ? 我们尝试去掉 super() 看看编译的结果: constructor() { this.state = {searchStr: ''}; this.handleChange = this.handleChange.bind(this); } 编译错误: 提示没有在this之前加上super() 其实就是少了super(),导致了this的 Reference Error class MyComponent extends React.Co

[React] Use React.ReactNode for the children prop in React TypeScript components and Render Props

Because @types/react has to expose all its internal types, there can be a lot of confusion over how to type specific patterns, particularly around higher order components and render prop patterns. The widest and most recommended element type is React

react 生命中期 props state区别 生命周期

npm安装create-react-app npx create-react-app my-app cd my-app npm start 查看调试props验证  babel-react-optimize 可以在调试环境下有propstype属性,在产品环境下自动去掉,以免占用浏览器资源,降低性能: defaultProps可以给组件设置默认的初始值. 组件.defaultProps={ initvalue:0 }prop和state的区别: prop是用于定义外部接口,state用于记录内部