スキップしてメイン コンテンツに移動

[TypeScript] Try React + Redux

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

コメント

このブログの人気の投稿

[Angular][ASP.NET Core] Upload chunked files

Intro I wanted to send files to Web application (made by ASP.NET Core). If the file size had been small, I didn't need do any special things. But when I tried to send a large file, the error was occurred by ASP.NET Core's limitation. Though I could change the settings, but I didn't want to do that, because I hadn't known the file sizes what would been actually using. So I splitted the data into chunks first, and sent them. After receiving all chunks, I merged them into one file. There might be some libraries or APIs (ex. Stream API) what did them automatically, but I couldn't find them. What I did [ASP.NET Core] Make CORS enabled [Angular] Split a large file into chunks [Angular][ASP.NET Core] Send and receive data as form data [ASP.NET Core] Merge chunks into one file [ASP.NET Core] Make CORS enabled Because the client side application(Angular) and the server side application(ASP.NET Core) had been separated, I had to make CORS(Cross-Origin Requests)

[Nest.js] Use WebSocket with ws

Intro Until last time , I had used node-web-rtc to try WebRTC. But because the example was a little complicated for I understood the core functions of using WebRTC. So I look for other frameworks or libraries. PeerJS is a famous library for WebRTC. peers/peerjs: Peer-to-peer data in the browser. - GitHub peers/peerjs-server: Server for PeerJS - GitHub PeerJS - Simple peer-to-peer with WebRTC A problem is I don't know how to integrate to the Nest.js project. I couldn't find examples. So I don't choose at least this time. What shall I choose? According MDN, WebRTC doesn't specify strictly what technology is used on server application for connecting two devices. Signaling and video calling - Web APIs | MDN But in many examples include MDN's one use WebSocket. samples-server/s/webrtc-from-chat at master · mdn/samples-server · GitHub So I try WebSocket in the Nest.js project. Use WebSocket in a Nest.js project Nest.js has a function for using We

[Nest.js] Show static files

Intro I wanted to use Nest.js and WebRTC(node-webrtc). NestJS - A progressive Node.js framework Documentation | NestJS - A progressive Node.js framework And because I wanted to try with simple page(not use JavaScript frameworks), I added static HTML, CSS, JavaScript into a Nest.js project. Prepare Install First, I installed @nestjs/cli. First steps | NestJS - A progressive Node.js framework As same as last time , I couldn't do global install because I had used Volta. But I could installed by volta. volta install @nestjs/cli Create project nest new nest-web-rtc-sample volta pin node@12 Run npm start After doing "npm start", I could getting "Hello World!" from http://localhost:3000. Add static files I could add static files by two ways. @nestjs/serve-static First one of them was using "serve-static". Serve Static | NestJS - A progressive Node.js framework npm install --save @nestjs/serve-static And I needed adding a module into app.modu