Skip to content
目录

实战篇 react 与组件类型

React 组件 与类型

源码地址

使用 npx 安装

bash
$ npx create-react-app ts-react-app --typescript

我们先简单了解一下 npx 是什么?

npx

npx 是一种在 npm 中的安装工具,我们平时使用 npm 比较多,在安装 npm@5.2.0+ 的版本时,会自动安装 npx。如果没有可以手动安装一下。

bash
$ npm install -g npx

调用项目安装的模块

npx 想要解决的主要问题是,调用项目内部安装的模块。比如,项目内部安装了测试工具 Mocha。

bash
$ npm install mocha -D

一般来说,调用 Mocha,只能在项目脚本和 package.json 的 script 字段里面,如果想在命令行下调用,必须像下面这样。

bash
# 项目根目录下执行

$ node_modules/.bin/mocha --version

npx 的原理很简单,就是运行的时候,会到 node_modules/.bin 路径和环境变量 $PATH 里面,检查命令是否存在。

由于 npx 会检查环境变量 $PATH,所以系统命令也可以调用。

bash
# 等于执行 ls

$ npx ls

需要注意,Bash 内置的命令不在 $PATH 里面。比如 cd,就不能用 npx cd

避免全局安装模块

npx 能避免全局安装模块。比如,create-react-app 这个模块是全局安装,npx 可以运行它,而且不进行全局安装。

bash
$ npx create-react-app my-react-app

npx 会将 create-react-app 下载到一个临时目录,再次执行该命令,会重新下载。

下载全局模块时,npx 允许制定版本。

bash
$ npx uglify-js@3.1.0 main.js -o ./dist/main.js

指定使用 3.1.0 版本的 uglify-js 压缩脚本。

注意,只要 npx 后面的模块无法在本地发现,它就会下载同名模块。比如,本地没有安装 http-server 模块,当执行以下命令会自动下载该模块,并在当前目录启动一个 Web 服务。

bash
$ npx http-server

--no-install 和 --ignore-existing

如果想让 npx 强制使用本地模块,不下载远程模块,可以添加 --on-install 参数。如果本地不存在该模块,会报错。

bash
$ npx http-server --on-install

如果想要忽略本地的同名模块,强制安装使用远程模块,可以添加 --ignore-existing 参数。比如,本地已经全局安装了 create-react-app,但还是想使用远程模块,就用这个参数。

bash
$ npx create-react-app my-react-app --ignore-existing

使用不同版本的 node

利用 npx 可以下载模块这个特点,我们可以指定某个版本的 node 运行脚本。

bash
$ npx node@8.0.0 -v

上面命令会使用 8.0.0 版本的 node 执行脚本。原理是从 npm 下载这个版本的 node,使用后再删除。

-p 和 -c

-p 参数用于指定 npx 所要安装的模块,对于需要安装多个模块的场景非常有用。

bash
$ npx -p lolcatjs -p cowsay [command]

如果 npx 安装多个模块,默认情况下,所执行的命令中,只有第一个可执行项会使用 npx 安装的模块,后面的可执行项还是会交给 Shell 解释。

bash
$ npx -p lolcatjs -p cowsay 'cowsay hello | lolcatjs'

上面的代码执行后,cowsay hello | lolcatjs 会报错,原因是第一项 cowsaynpx 解释,而第二项命令由 Shell 解释,但是 lolcatjs 并没有 全局安装,所以会报错。

这个问题可以用 -c 参数来解决,-c 参数的另一个作用,是将环境变量带入所有要执行的命令。举例来说,npm 提供当前项目的一些环境变量,可以用下面的命令查看。

bash
$ npm run env | grep npm_

-c 参数可以把这些 npm 的环境变量带入 npx 命令.

bash
$ npx -c 'echo "$npm_package_name"'

上面代码会输出当前项目的项目名。

执行 GitHub 源码

npx 还可以执行 GitHub 上面的模块源码。

bash
# 执行 Gist 代码
$ npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

# 执行仓库代码
$ npx github:piuccio/cowsay hello

注意,远程代码必须是一个模块,即必须包含 package.json 和入口脚本。

安装其他依赖包

我们使用 yarn

bash
# 依赖
$ yarn add antd react-router-dom @types/react-router-dom

# 开发依赖
$ yarn add babel-plugin-import customize-cra http-proxy-middleware http-server react-app-rewired --dev

简单介绍以上包的作用

babel-plugin-import 可以实现 antd 按需加载。

customize-crareact-app-rewired 可以帮助我们实现 create-react-app 脚手架的自定义。

http-proxy-middlewarehttp-server 可以帮助我们搭建一个 mock server。

创建函数组件和类组件

函数组件

React 可以通过函数或类的形式创建组件,接下来实现一个函数组件的示例。

js
import React from 'react';
import { Button } from 'antd';

interface Greeting {
  name: string;
}

const HelloFn = (props: Greeting) => <Button>Hello {props.name}</Button>;

HelloFn.defaultProps = {
  name: 'React Function',
};

export default HelloFn;

defaultProps 可以为接口提供默认值。

除了上面这种直接定义函数的方式之外,React 声明文件中对函数组件单独定义了一个类型 React.FC

js
const HelloFn: React.FC<Greeting> = ({ name, children }) => (
  <Button>Hello {name}</Button>
);

使用 React.FC 优点是,它的参数中隐含提供了 children 属性,在调用 defaultProps 方法时,编译器会提示,但是为它设置默认属性时,对应接口中定义的属性需要设置成可选属性

综合来讲,我们推荐直接定义函数的方式来实现函数组件。

类组件

类组件需要继承 React.Component,在 React.Component 的子类中有个必须定义的 render() 函数。

js
import React, { Component } from 'react';
import { Button } from 'antd';

interface Greeting {
  name: string;
}

interface HelloState {
  count: number;
}

class HelloClass extends Component<Greeting, HelloState> {
  state: HelloState = {
    count: 0,
  };
  static defaultProps = {
    name: 'React Class',
  };
  render() {
    return (
      <>
        <p>count: {this.state.count}</p>
        <Button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Hello {this.props.name}
        </Button>
      </>
    );
  }
}

export default HelloClass;

在 TypeScript 中 Component 被定义为泛型类,它有三个参数,第一个参数表示这个类属性的类型,第二个表示状态类型,第三个参数为 snapshot。

高阶组件和 Hooks

React 组件演化

组件复用方式优势劣势状态
类组件(class)发展时间长,接受度广泛只能继承父类传统模式,长期存在
Mixin可以复制任意对象的任意多个方法组件相互依赖、耦合,可能产生冲突,不利于维护抛弃
高阶组件(HOC)利用装饰器模式,在不改变组件的基础上,动态为其添加新的能力嵌套过多调试困难,需要遵循某些约定(不改变原始组件,必须要透传 props 等)能力强大,应用广泛
Hooks代替 class,多个 Hooks 互不影响,避免嵌套低于,开发效率高切换到新思维需要成本React 的未来

高阶组件(HOC)

js
import React from 'react';

import HelloClass from './hello-class';

interface Loading {
  loading: boolean;
}

function HelloHoc<P>(WrappedComponent: React.ComponentType<P>) {
  return class extends React.Component<P & Loading> {
    render() {
      const { loading, ...props } = this.props;
      return loading ? (
        <div>Loading ...</div>
      ) : (
        <WrappedComponent {...(props as P)} />
      );
    }
  };
}

export default HelloHoc(HelloClass);

Hooks

js
import React, { useState, useEffect } from 'react';
import { Button } from 'antd';

interface Greeting {
  name: string;
}

const HelloHooks = (props: Greeting) => {
  const [count, setCount] = useState(0);
  const [text, setText] = (useState < string) | (null > null);

  useEffect(() => {
    if (count > 5) {
      setText('stop!!!');
    }
  });

  return (
    <>
      <p>
        你点击了 {count}{text}
      </p>
      <Button onClick={() => setCount(count + 1)}>Hello {props.name}</Button>
    </>
  );
};

HelloHooks.defaultProps = {
  name: 'React Hooks',
};

export default HelloHooks;