# React 组件与类型

源码地址

使用 npx 安装

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

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

# npx

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

$ npm install -g npx

# 调用项目安装的模块

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

$ npm install mocha -D

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

# 项目根目录下执行

$ node_modules/.bin/mocha --version

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

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

# 等于执行 ls

$ npx ls

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

# 避免全局安装模块

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

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

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

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

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

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

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

$ npx http-server

# --no-install 和 --ignore-existing

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

$ npx http-server --on-install

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

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

# 使用不同版本的node

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

$ npx node@8.0.0 -v

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

# -p 和 -c

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

$ npx -p lolcatjs -p cowsay [command]

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

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

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

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

$ npm run env | grep npm_

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

$ npx -c 'echo "$npm_package_name"'

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

# 执行GitHub源码

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

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

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

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

# 安装其他依赖包

我们使用 yarn

# 依赖
$ 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 可以通过函数或类的形式创建组件,接下来实现一个函数组件的示例。

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

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

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

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

# 类组件

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

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)

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

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
Last Updated: 10/31/2019, 8:34:53 PM