Intro
[TypeScript] Try React 1
This time, I tried Redux to manage props and states.
Manage by React
First, I tried manging props and states by React.
Manage in child components(Square.tsx)
If only in the child components(Square.tsx) used the states, I could let them manage the states.
SquareValue.ts
export type SquareValue = '◯'|'✕'|null;
Square.tsx
import React from "react";
import './Square.css';
import { SquareValue } from './SquareValue';
export type SquareProps = {
key: number,
value: SquareValue,
onClick: () => void,
};
export type SquareState = {
value: SquareValue,
}
export class Square extends React.Component<SquareProps, SquareState> {
render() {
return (<button className="square"
onClick={() => this.setState({ value: '✕'})}>
{this.state?.value}
</button>);
};
}
When I clicked the buttons, they would show '✕'.
Manage in the parent component(Board.tsx)
If the parent component(Board.tsx) also needed the states?
It could create a state that had the child states.
Board.tsx
import React from "react";
import { Square } from "./Square";
import './Board.css';
import { SquareValue } from './SquareValue';
export type BoardState = {
nextIsX: boolean,
// child states
squares: Array<SquareValue>
};
export class Board extends React.Component<any, BoardState> {
constructor(props: any){
super(props);
this.state = {
nextIsX: true,
squares: Array(9).fill(null),
};
}
public render(): JSX.Element {
return (
<div className="board-row">
{Array.from(Array(9).keys())
.map(i => this.renderSquare(i))}
</div>
);
}
private renderSquare(i: number): JSX.Element {
return (<Square key={i}
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}>
</Square>);
}
private handleClick(i: number) {
const newSquares = this.state.squares.slice();
newSquares[i] = (this.state.nextIsX)? '✕': '◯';
const newState = {
nextIsX: ! this.state.nextIsX,
squares: newSquares
};
this.setState(newState);
}
}
Square.tsx
...
export class Square extends React.Component<SquareProps> {
render() {
return (<button className="square"
onClick={() => this.props.onClick()}>
{this.props.value}
</button>);
};
}
Because the parent component managed the child states, the child components could be changed function.
Square.tsx
...
export function renderSquare(props: SquareProps): JSX.Element {
return (<button key={props.key} className="square"
onClick={() => props.onClick()}>
{props.value}
</button>);
}
Board.tsx
...
private renderSquare(i: number): JSX.Element {
return (renderSquare({
key: i,
value: this.state.squares[i],
onClick: () => this.handleClick(i)
}));
}
...
Tutorial: Intro to React – React
There were some problems.
One was the parent component had many responsibilities.
It had to control child components, props and states.
To manage their states, I tried to use Redux.
Use Redux
Redux - A predictable state container for JavaScript apps. | Redux
React Redux · Official React bindings for Redux
Install
Because create-react-app could only create a React + TypeScript project or a React + Redux project.
So I added Redux manually.
npm install --save redux react-redux
npm install --save-dev @types/react-redux
Following Basic Tutorial, I added Actions, ActionCreator, Reducer, and Store(connect).
Basic Tutorial: Intro | Redux
Actions & ActionCreator
Actions | Redux
First, I defined "What's happened?".
In this sample, I defined "Clicking a Square button".
click-action.ts
export interface ClickAction {
type: 'CLICK_SQUARE';
id: number;
}
And I added an ActionCreator.
It created Action as same as factory methods.
click-action.ts
...
export function clickSquare(id: number): ClickAction {
return {
type: 'CLICK_SQUARE',
id: id
};
}
Reducer
I added Reducer.
It decided how was the state changed by Action.
Reducers | Redux
board-state-reducer.ts
import { BoardState } from "./Board";
import { ClickAction } from "./click-action";
/** if the state was null, set default value */
function initialState(): BoardState {
return {
nextIsX: true,
squares: Array(9).fill(null),
};
}
export function updateSquare(state: BoardState = initialState(), action: ClickAction): BoardState {
if (state == null || action == null) {
return state;
}
if (action.type != 'CLICK_SQUARE') {
return state;
}
const squares = state.squares.slice();
squares[action.id] = (state.nextIsX)? '✕': '◯';
return {
nextIsX: ! state.nextIsX,
squares
};
}
Store
Next, I tried adding a Store.
It managed the state.
Store | Redux
To use Redux in components, I used react-redux.
Usage with React | Redux
React Redux · Official React bindings for Redux
In react-redux, Store was created in index.tsx and set into Provide.
index.tsx
import 'raf/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux';
import { updateSquare } from './tic-tac-toe/board-state-reducer';
import { Provider } from 'react-redux';
// set Reducers as argument of createStore.
const store = createStore(updateSquare);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
In this sample, I set only one Reducer as argument of createStore.
But I also could multiple Reducers.
connect
For using the state in components, I had to use "connect".
Board.tsx
import 'react-app-polyfill/ie11';
import 'react-app-polyfill/stable';
import React, { Dispatch } from "react";
import { renderSquare } from "./Square";
import './Board.css';
import { clickSquare, ClickAction } from './click-action';
import { connect } from 'react-redux';
import { SquareValue } from './SquareValue';
export type BoardState = {
nextIsX: boolean,
squares: Array<SquareValue>
};
/** return state what was the state of this component */
function mapStateToProps(state: BoardState): BoardState {
return state;
}
/** return changing the state Actions */
function mapDispatchToProps(dispatch: Dispatch<ClickAction>) {
return {
onClick: (id: number) => dispatch(clickSquare(id))
};
}
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
class Board extends React.Component<Props, BoardState> {
public render(): JSX.Element {
return (
<div className="board-row">
{Array.from(Array(9).keys())
.map(i => this.renderSquare(i))}
</div>
);
}
private renderSquare(i: number): JSX.Element {
return (renderSquare({
key: i,
value: this.props.squares[i],
onClick: () => this.handleClick(i)
}))
}
private handleClick(i: number) {
if (this.props.squares[i] != null) {
return;
}
this.props.onClick(i);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Board);
Although the parent component(App.tsx) use "<Board></Board>", this component had to export connect. not the component class.
Todo App with Redux - CodeSandbox
コメント
コメントを投稿