sidebarDepth |
---|
3 |
使用 npx
安装
$ npx create-react-app ts-react-app --typescript
我们先简单了解一下 npx
是什么?
npx
是一种在 npm
中的安装工具,我们平时使用 npm
比较多,在安装 [email protected]+
的版本时,会自动安装 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 [email protected] main.js -o ./dist/main.js
指定使用 3.1.0 版本的 uglify-js
压缩脚本。
注意,只要 npx
后面的模块无法在本地发现,它就会下载同名模块。比如,本地没有安装 http-server
模块,当执行以下命令会自动下载该模块,并在当前目录启动一个 Web 服务。
$ npx http-server
如果想让 npx
强制使用本地模块,不下载远程模块,可以添加 --on-install
参数。如果本地不存在该模块,会报错。
$ npx http-server --on-install
如果想要忽略本地的同名模块,强制安装使用远程模块,可以添加 --ignore-existing
参数。比如,本地已经全局安装了 create-react-app
,但还是想使用远程模块,就用这个参数。
$ npx create-react-app my-react-app --ignore-existing
利用 npx
可以下载模块这个特点,我们可以指定某个版本的 node 运行脚本。
$ npx [email protected] -v
上面命令会使用 8.0.0 版本的 node 执行脚本。原理是从 npm
下载这个版本的 node,使用后再删除。
-p
参数用于指定 npx
所要安装的模块,对于需要安装多个模块的场景非常有用。
$ npx -p lolcatjs -p cowsay [command]
如果 npx
安装多个模块,默认情况下,所执行的命令中,只有第一个可执行项会使用 npx
安装的模块,后面的可执行项还是会交给 Shell 解释。
$ npx -p lolcatjs -p cowsay 'cowsay hello | lolcatjs'
上面的代码执行后,cowsay hello | lolcatjs
会报错,原因是第一项 cowsay
由 npx
解释,而第二项命令由 Shell 解释,但是 lolcatjs
并没有 全局安装,所以会报错。
这个问题可以用 -c
参数来解决,-c
参数的另一个作用,是将环境变量带入所有要执行的命令。举例来说,npm 提供当前项目的一些环境变量,可以用下面的命令查看。
$ npm run env | grep npm_
-c
参数可以把这些 npm 的环境变量带入 npx
命令.
$ npx -c 'echo "$npm_package_name"'
上面代码会输出当前项目的项目名。
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-cra
和 react-app-rewired
可以帮助我们实现 create-react-app
脚手架的自定义。
http-proxy-middleware
和 http-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。
组件复用方式 | 优势 | 劣势 | 状态 |
---|---|---|---|
类组件(class) | 发展时间长,接受度广泛 | 只能继承父类 | 传统模式,长期存在 |
Mixin | 可以复制任意对象的任意多个方法 | 组件相互依赖、耦合,可能产生冲突,不利于维护 | 抛弃 |
高阶组件(HOC) | 利用装饰器模式,在不改变组件的基础上,动态为其添加新的能力 | 嵌套过多调试困难,需要遵循某些约定(不改变原始组件,必须要透传 props 等) | 能力强大,应用广泛 |
Hooks | 代替 class,多个 Hooks 互不影响,避免嵌套低于,开发效率高 | 切换到新思维需要成本 | React 的未来 |
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)
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