使用 create-react-app 开启 TypeScript
Create React App 是一个官方支持的创建 React 单页应用程序的CLI,它提供了一个零配置的现代构建设置。当你使用 Create React App 来创建一个新的 TypeScript React 工程时,你可以运行
npx create-react-app my-app --typescript
yarn create react-app my-app --typescript
如果在已有的工程中添加,也非常简单:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
或者
yarn add typescript @types/node @types/react @types/react-dom @types/jest
从零配置
创建 index.html
文件,以及src
目录,在 src
目录中创建 index.tsx
。
TypeScript 的文件格式是 tsx
接下来安装必要的包和配置 package.json
文件:
"scripts": {
"dev": "MODE=development webpack -w --mode=development",
"build": "MODE=production webpack --mode=production"
},
"dependencies": {
"@types/react": "^16.8.13",
"@types/react-dom": "^16.8.3",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"devDependencies": {
"awesome-typescript-loader": "^5.2.1",
"source-map-loader": "^0.2.4",
"typescript": "^3.4.3",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
}
创建 tsconfig.json
和 webpack.config.js
文件:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["dom","es2015"],
"jsx": "react",
"sourceMap": true,
"strict": true,
"noImplicitAny": true,
"baseUrl": "src",
"paths": {
"@/*": ["./*"],
},
"esModuleInterop": true,
"experimentalDecorators": true,
},
"include": [
"./src/**/*"
]
}
- jsx 选择
react
- lib 开启
dom
和 es2015
- include 选择我们创建的
src
目录
var fs = require('fs')
var path = require('path')
var webpack = require('webpack')
const { CheckerPlugin } = require('awesome-typescript-loader');
var ROOT = path.resolve(__dirname);
var entry = './src/index.tsx';
const MODE = process.env.MODE;
const plugins = [];
const config = {
entry: entry,
output: {
path: ROOT + '/dist',
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.ts[x]?$/,
loader: [
'awesome-typescript-loader'
]
},
{
enforce: 'pre',
test: /\.ts[x]$/,
loader: 'source-map-loader'
}
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
alias: {
'@': ROOT + '/src'
}
},
}
if (MODE === 'production') {
config.plugins = [
new CheckerPlugin(),
...plugins
];
}
if (MODE === 'development') {
config.devtool = 'inline-source-map';
config.plugins = [
new CheckerPlugin(),
...plugins
];
}
module.exports = config;
类组件的使用
类组件是目前来说使用的最频繁的一种,因此我们需要了解到它。
Props 和 State
首先创建 Props 和 State 接口,Props 接口接收一个 name 参数,State 接口接收 color:interface IProps {
name: string;
}
interface IState {
color: "red" | "blueviolet"
}
class Home extends React.Component<IProps, IState> {
constructor(props: IProps){
super(props);
this.state = {
color: "red"
}
}
public onClickColor = () => {
const { color } = this.state;
if (color === "red") {
this.setState({
color: "blueviolet"
});
}
if (color === "blueviolet") {
this.setState({
color: "red"
});
}
}
public render(){
const { name } = this.props;
const { color } = this.state;
return (
<div>
<span style={{ color }}>{ name }</span>
<button onClick={this.onClickColor}>变颜色</button>
</div>
);
}
}
export default Home;
在 App
中使用 Home
组件时我们可以得到明确的传递参数类型。
处理 Event 对象
有时候我们需要处理一下 Event 对象,一般 change 事件我们可以使用 React.ChangeEvent
,click 事件可以使用 React.MouseEvent
,它们都接收一个 Element
,如:
onClickColor = (ev: React.MouseEvent<HTMLButtonElement>) => {
PureComponent
我们都知道 React
的刷新机制,因此如果每一次的变动都要刷新一下界面,这对于应用程序的性能来说是一个非常不科学的事情,因此在没有 PureComponent
之前,我们都需要手动使用 shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
来确认到底要不要刷新界面,如:
import * as React from "react";
import Typography from "@material-ui/core/Typography";
interface IMyComparisonProps {
text: string;
}
class MyComparison extends React.Component<IMyComparisonProps> {
constructor(props: IMyComparisonProps) {
super(props);
}
public shouldComponentUpdate(nextProps: IMyComparisonProps) {
if (this.props.text === nextProps.text) {
return false;
}
return true;
}
public render() {
const { text } = this.props;
return (
<Typography>
Component 值:{ text }
</Typography>
);
}
}
export default MyComparison;
如果返回的是 false
那么将不调用 render
,如果是 true
则调用 render
。
但是如果我们使用 PureComponent
那么就省略了这一步,我们可以不用关心组件是否要刷新,而是 React.PureComponent
来帮我们决定。在使用之前,我们还有一些注意事项要了解,React.PureComponent
是一个和 React.Component
几乎相同,唯一不同的是 React.PureComponent
帮助我们完成了 shouldComponentUpdate
的一些交浅的比较,因此在我们真实的组件设计中,我们一般会用于最后一个关键点的组件上。
Portals
ReactDOM
中提供了一个方法 createPortal
,可以将节点渲染在父组件之外,但是你可以依然使用父组件上下文中的属性。这个特性在我所讲的全局对话框或者提示框中非常有用,它脱离了父节点的容器,插在最外层,在样式上就能通过 position: fixed
来覆盖整个文档树。
我们在 state
中定义了一个 open
,它只接收一个布尔值,用于打开提示框或关闭提示框架,如:
export interface IPortalsProps {}
export interface IPortalsState {
open: boolean;
}
然后我们定义两个方法用于设置 open
:
public clickHandler = () => {
this.setState({
open: true,
});
}
public clickHandlerClose = () => {
this.setState({
open: false,
});
}
最后在 render
方法中使用 ReactDOM.createPortal
来创建一个全局的 Alert
,如:
import * as React from "react";
import * as ReactDOM from "react-dom";
import Button from "@material-ui/core/Button";
import Alert from "../Alert";
import {
IPortalsProps,
IPortalsState,
} from "./types";
class MyPortals extends React.Component<IPortalsProps, IPortalsState> {
constructor(props: IPortalsProps) {
super(props);
this.state = {
open: false,
};
}
public clickHandler = () => {
this.setState({
open: true,
});
}
public clickHandlerClose = () => {
this.setState({
open: false,
});
}
public render() {
const { open } = this.state;
return (
<div>
<Button
variant="outlined"
color="primary"
onClick={this.clickHandler}
>
提示
</Button>
{
ReactDOM.createPortal(
<Alert
open={open}
message="React Component Portals Use"
handleClose={this.clickHandlerClose}
/>,
document.getElementById("app")!,
)
}
</div>
);
}
}
export default MyPortals;
Fragments
Fragments 可以让我们减少生成过多有副作用的节点,以往 render
必须返回单一节点,因此很多组件常常会产生过多无用的 div
,React
根据这样的情况给予了一个组件来解决这个问题,它就是 Fragment
。
public render(){
return (
<React.Fragment>
<div></div>
<div></div>
</React.Fragment>
)
}
函数组件以及 Hooks
Hooks 自去年10月发布以来,函数组件就派上了用场,React 的函数组件主要引用 SFC
返回(React.FunctionComponent
),当然你也可以不引用 SFC
类型只不过返回的是(JSX.Element
),这就是区别。
useState
以前:
interface IFuncComp {
name: string;
}
const FuncComp: React.SFC<IFuncComp> = ({ name }) => {
return (
<div>{ name }</div>
)
}
现在:
interface IFuncComp2 {
name: string;
}
const FuncComp2: React.SFC<IFuncComp2> = ({ name }) => {
const [ num, setNum ] = React.useState<number>(0);
return (
<div>
{ name } { num }
<button onClick={() => {
setNum(num + 1);
}}>+</button>
</div>
)
}
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
由于 useState
被定义为一个泛型函数,因此类型可以由我们自己来指定。
useEffect
当你使用 useEffect
时,我们可以传入第三个参数来决定是否执行这个 callback ,这对于优化你的应用至关重要。
React.useEffect(() => {
}, [num]);
useContext
对于 useContext
当你需要共享数据时可用:
interface IContext {
name: string;
}
const initContext: IContext = {
name: "",
};
const context = React.createContext(initContext);
const FuncMainContext = () => {
return (
<>
<context.Provider value={initContext}>
<FuncContext />
</context.Provider>
</>
)
}
const FuncContext = () => {
const va = React.useContext(context);
return (
<div>{ va.name }</div>
)
}
useReducer
如果你已经习惯 redux 不妨来看看 useReducer
,假设我们需要通过按钮来更改文本颜色:
interface IState {
color: "red" | "blueviolet"
}
interface IAction {
type: string;
payload: any;
}
const reducer = (prevState: IState, action: IAction) => {
const { type, payload } = action;
switch(type){
case "COLOR_CHANGE" : {
return { ...prevState, color: payload };
}
default: {
return prevState;
}
}
}
const App = () => {
const initialState: IState = {
color: "red"
}
const [state, dispatch ] = React.useReducer(reducer, initialState);
return (
<div>
<span style={{ color: state.color }}>icepy</span>
<button onClick={() => {
dispatch({
type: "COLOR_CHANGE",
payload: state.color === "red" ? "blueviolet" : "red"
});
}}>change</button>
</div>
);
}
useRef
当我们需要来引用原生DOM来处理某件事情时,useRef
可以辅助我们完成这项工作:
const App = () => {
const inputEl = React.useRef<HTMLInputElement>(null);
const onButtonClick = () => {
if (inputEl && inputEl.current) {
inputEl.current.focus();
}
}
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus</button>
</>
);
}
useMemo
接下来我们可以说一说 useMemo
,这只能当作一次性能优化的选择,通常情况下假设我们的 state
有两个属性,它的场景可能如下:
const App = () => {
const [ index, setIndex ] = React.useState<number>(0);
const [ str, setStr ] = React.useState<string>("");
const add = () => {
return index * 100;
}
return (
<>
<div>{index}-{str}-{add()}</div>
<div>
<button onClick={() => {
setIndex(index + 1);
}}>+</button>
<input type="text" onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {
setStr(ev.target.value);
}}/>
</div>
</>
);
}
请发表评论