文章目录
reference
国外资料:
https://blog.logrocket.com/how-why-a-guide-to-using-typescript-with-react-fffb76c61614/
https://alligator.io/react/typescript-with-react/
https://ts.chibicode.com/todo/
[ts学习资料,短小精悍] https://2ality.com/2018/04/type-notation-typescript.html#what-you%E2%80%99ll-learn
国内资料:
https://zhuanlan.zhihu.com/p/42141179
https://www.bilibili.com/video/BV1z4411W7wa?p=5
官方资料:
https://create-react-app.dev/docs/adding-typescript/#getting-started-with-typescript-and-react
https://react-typescript-cheatsheet.netlify.app/docs/basic/setup
https://www.typescriptlang.org/docs/handbook/react.html
summary
1 用脚手架安装载有ts的react项目
在最新的create-react-app中,官方推荐卸载本地的“create-react-app” 包
npm uninstall -g create-react-app
换成npx create-react-app myprpject,这样以保证使用最新版本的脚手架,并且可以使用模板
npx create-react-app myprpject --template cra-react-*
关于具体的模板,可以在npm搜索“cra-react-*”
npx create-react-app myprpject --template cra-react-typescript
npx create-react-app myprpject --typescript
上述两种方法应该都可以,我忘了我用的哪种
2 新的用法
2.1 import
新的引入方式
import * as React from 'react';
import * as ReactDOM from 'react-dom';
ReactDOM.render(
<div>Hello world</div>,
document.getElementById('root')
);
2.2 Function 组件
写法1
import * as React from 'react';
type IAppProps {
message?: string
}
const App = (props: IAppProps) => {
return <div>{props.message}</div>
}
export default App;
写法2
React.FunctionComponent or React.FC
const App: React.FunctionComponent = (props: IAppProps) => {
return <div>{props.message}</div>
}
这两者的区别有什么呢?
-
React.FunctionComponent is explicit about the return type, while the normal function version is implicit (or else needs additional annotation).
-
It provides typechecking and autocomplete for static properties like displayName, propTypes, and defaultProps.
- Note that there are some known issues using defaultProps with React.FunctionComponent. See this issue for details. We maintain a separate defaultProps section you can also look up.
-
It provides an implicit definition of children (see below) - however there are some issues with the implicit children type (e.g. DefinitelyTyped#33006), and it might considered better style to be explicit about components that consume children, anyway.
const Title: React.FunctionComponent<{ title: string }> = ({ children, title, }) => <div title={title}>{children}</div>;
-
In the future, it may automatically mark props as readonly, though that’s a moot point if the props object is destructured in the parameter list.
2.3 Class 组件
type MyProps = {
// using `interface` is also ok
message: string;
};
type MyState = {
count: number; // like this
};
class App extends React.Component<MyProps, MyState> {
state: MyState = {
// optional second annotation for better type inference
count: 0,
};
render() {
return (
<div>
{this.props.message} {this.state.count}
</div>
);
}
}
类属性: 如果你需要声明一个类属性在之后使用, 请只是声名它, 不要给它赋值
class App extends React.Component<{message: string;}> {
pointer: number; // like this,先不要赋值
componentDidMount() {
this.pointer = 3; //在这里赋值
}
render() {
return (
<div>
{this.props.message} and {this.pointer}
</div>
);
}
}
小总结一下
import * as React from "react";
type IProps = {
count?: number;
};
type IState = Readonly<{
count: number;
}>;
const PhotoGallery: React.FunctionComponent<IProps> = (props) => {
const [count, setCount] = React.useState<IState>({
count: 0
});
const handleClick = () => {
setCount({
count: (count as IState).count + 1
})
}
return (
<>
<h1>{props.count}</h1>
<h1>{count.count}</h1>
<button onClick={handleClick}>add one</button>
</>
)
}
class PhotoGallery extends React.Component<IProps, IState> {
/* 这里为什么要声明两边IState呢
严格来说,注释state类属性不是必需的,但是在访问this.state并初始化状态时,它可以提供更好的类型推断。
这是因为它们以两种不同的方式工作,第二个泛型类型参数将允许this.setState()正常工作,因为该方法来自基类,但是组件内部的初始化状态会覆盖基实现,因此您必须 确保您告诉编译器您实际上没有做任何不同的事情。
*/
/*
ES6的class在构造函数中声明属性,因此需要构造函数。
ts的class可在class体内直接声明属性,因此不一定需要构造函数。
*/
state: IState = {
count: 0,
};
// 这里使用箭头函数可以免去函数绑定
handleClick = () => {
this.setState({
count: this.state.count + 1,
});
}
render () {
return (
<>
<h1>{this.props.count}</h1>
<h1>{this.state.count}</h1>
<button onClick={this.handleClick}>add one</button>
</>
);
}
}
export default PhotoGallery;
2.4 Hooks
useState
类型自动推断在大多时候是很好用的,然而,许多hook会被初始化为Null,这时候你会考虑如何提供类型。所以你需要明确类型,并且使用联合类型。
// 自动类型推断
const [val, toggle] = React.useState(false); // `val` is inferred to be a boolean, `toggle` only takes booleans
// 手动类型
const [user, setUser] = React.useState<IUser|null>(null);
//later..
setUser(newUser);
其他Hook请参考
https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/hooks
2.5 Types or Interface?
interfaces are different from types in TypeScript, but they can be used for very similar things as far as common React uses cases are concerned. Here’s a helpful rule of thumb:
- always use interface for public API’s definition when authoring a library or 3rd party ambient type definitions.
- consider using type for your React Component Props and State, because it is more constrained.
Types are useful for union types (e.g. type MyType = TypeA | TypeB) whereas Interfaces are better for declaring dictionary shapes and then implementing or extending them.
2.6 Forms and Events
如果不考虑表现,行内handler是最简单的方式,ts可以只用类型推断和上下文来得出是哪种Event
const el = (
<button
onClick={(event) => {
/* ... */
}}
/>
);
如果你需要更明确的定义你的handler的类型,IDE工具是一个非常好的帮手,你可以在自动补全中找到你想要的Event类型
class App extends React.Component<{}, {text: string;}> {
state = {
text: "",
};
// typing on RIGHT hand side of =
onChange = (e: React.FormEvent<HTMLInputElement>): void => {
this.setState({ text: e.currentTarget.value });
};
render() {
return (
<div>
<input type="text" value={this.state.text} onChange={this.onChange} />
</div>
);
}
}
你也可以不用上面这种定义了参数类型和返回类型分别为React.FormEvent<> and void 的方式。你也可以给handler本身定义类型。
// typing on LEFT hand side of '='
onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({text: e.currentTarget.value})
}
Typing onSubmit, with Uncontrolled components in a Form
如果你不太在意 event 的类型, 你可以只使用 React.SyntheticEvent (混合Event). If your target form has custom named inputs that you’d like to access, you can use type widening (类型扩张):
<form
ref={formRef}
onSubmit={(e: React.SyntheticEvent) => {
e.preventDefault();
const target = e.target as typeof e.target & {
email: { value: string };
password: { value: string };
};
const email = target.email.value; // typechecks!
const password = target.password.value; // typechecks!
// etc...
}}
>
<div>
<label>
Email:
<input type="email" name="email" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Log in" />
</div>
</form>