react入门小demo(TS版本)
基于官方的入门demo进行TS改造
准备
初始化脚手架
// 执行以下命令就能看到在当前目录my-app文件夹
npx create-react-app my-app --template typescript
找到my-app文件夹
// 启动脚手架
yarn start
初始化demo
- 将以下代码复制到app.css中
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol, ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
- 将js复制到app.ts中
class Square extends React.Component {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
export default Game;
为Props添加类型声明
我们迎来了类型系统的第一顿毒打,系统告诉我们这一行,没有匹配类型
(props: Readonly<{}>): Square
(props: {}, context?: any): Square
<Square value={i}/>
这里一行与上述的类型声明不匹配,虽然我也看了TS一天,但是没发现上面的语法,官方也没看到和TS结合的例子,这个时候把错误信息复制到百度上去,解决方案就有了
只需要这样
class Square extends React.Component<any>
解决了这个问题,我就发现,原来是因为我们Square组件的props类型没声明导致的,而reactReact.Component
// 使用接口描述对象的类型
interface SquareProps {
value: number
}
// 这里使用此接口
class Square extends React.Component<SquareProps> {
render() {
return (
<button className="square">
{ this.props.value }
</button>
);
}
}
我们的代码正常运行了,继续按照官网实例完善代码
给组件添加交互功能
按照官方例子做,没有出现什么问题
存储点击状态
我们需要存储单元格的点击状态,需要使用state,就像以下代码
class Square extends React.Component {
constructor(props) {
// 这里必须的
super(props);
// 初始化state
this.state = {
value: null,
};
}
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
使用state状态
class Square extends React.Component {
constructor(props) {
// 这里必须的
super(props);
// 初始化state
this.state = {
value: null,
};
}
render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{this.state.value}
</button>
);
}
}
constructor props类型
新的类型错误出现了,props是一个隐式any类型,我们需要给它一个类型
class Square extends React.Component<SquareProps> {
// 这里加上props类型
constructor(props: SquareProps) {
super(props)
this.state = {
value: null
}
}
render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{ this.state.value }
</button>
);
}
}
state类型
state中没有value,显而易见的是,我们需要为state声明类型,就想这样
interface SquareProps {
value: number
}
interface SquareState {
value: string | null
}
class Square extends React.Component<SquareProps,SquareState> {
constructor(props: SquareProps) {
super(props)
this.state = {
value: null
}
}
render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{ this.state.value }
</button>
);
}
}
提取函数到类,设置函数类型
class Square extends React.Component<SquareProps,SquareState> {
constructor(props: SquareProps) {
super(props)
this.state = {
value: null
}
}
clickHandler = (e): void => {
this.setState({value: 'X'})
}
render() {
return (
<button className="square" onClick={(e) => this.clickHandler(e)}>
{ this.state.value }
</button>
);
}
}
event缺少类型,这里注意react的事件是合成事件不是原始事件
// error
clickHandler = (e: MouseEvent): void => {
this.setState({value: 'X'})
}
// correct
clickHandler = (e: React.MouseEvent): void => {
this.setState({value: 'X'})
}
将状态提升到Board
这一步需要将状态提升至Board组件
interface BoardProps {
}
interface BoardState {
squares: string []
}
class Board extends React.Component<BoardProps, BoardState> {
// 编写constructor并且初始化state
constructor(props: BoardProps) {
super(props)
this.state = {
squares: Array(9).fill(null),
}
}
handlerClick= (i: number) =>{
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares});
}
renderSquare(i: number) {
// 传递onClick事件给子组件
return <Square value={this.state.squares[i] } onClick={() => this.handlerClick(i)}/>;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
删除Square组件状态
Square组件的状态已经被提升到了Board组件,我们需要删除对应的状态
interface SquareProps {
value: string
onClick: () => void
}
// SquareState也可以删除
interface SquareState {
value: string | null
}
class Square extends React.Component<SquareProps> {
// 删除constructor
// constructor(props: SquareProps) {
// super(props)
// this.state = {
// value: null
// }
// }
// clickHandler也可以删除
// clickHandler = (e: React.MouseEvent): void => {
// this.setState({value: 'X'})
// }
render() {
return (
<button className="square" onClick={(e) => this.props.onClick()}>
{ this.props.value }
</button>
);
}
}
改造Square组件为函数组件
// 由于 无状态,所以改造成函数组件
function Square(props: SquareProps) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
总结
作为一个前端,Typescript主要被用在视图层,Vue2和TS结合不太友好,特别是在this的处理上,React与TS结合体验很好,适合用来学习TS,同时简单了解一下react,开拓一下思路。