Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React-Redux-Redux-Saga-Workshop01

 React-Redux-Redux-Saga-Workshop01

From TIS internal workshop at 2019/7/22

tashxii

July 22, 2019
Tweet

More Decks by tashxii

Other Decks in Programming

Transcript

  1. React / Redux / Redux-Saga • React … UIライブラリ •

    Redux … React の状態管理ライブラリ • Redux-Saga … 非同期処理を扱うReduxのミドルウェア 2 https://ja.reactjs.org/ https://Redux-Saga.js.org/ https://redux.js.org/
  2. 題材に使うアプリケーション • タスク管理アプリ • イメージ(gif) • 機能 • サインアップ •

    ログイン • ボード管理 • ユーザー管理 • タスク管理 • ドラッグ&ドロップ 3
  3. 題材のアプリで使用しているライブラリ • Redux 状態管理ライブラリ • Redux-Saga 非同期処理用ライブラリ • styled-component コンポーネントのスタイル管理

    • Ant design コンポーネントライブラリ • react-beautiful-dnd ドラッグ&ドロップコンポーネント • react-router-dom URL遷移 • Font awesome アイコンライブラリ 4
  4. ソースコード • Front-end (React) https://github.com/tashxii/taskboard-react • Back-end (Go) https://github.com/tashxii/taskboard-api-go git

    clone https://github.com/tashxii/taskboard-react.git cd taskboard-react yarn install git clone https://github.com/tashxii/taskboard-api-go.git cd taskboard-api-go dep ensure go build 5
  5. Popular Front-end Frameworks • 2018年のhttps://2018.stateofjs.com/front-end-frameworks/overview/ 調べ 今使っているが、 また使いたい 今使っているが もう使いたくない

    聞いたことがあり、 習いたい 聞いたことはある が、興味はない 聞いたことはある が、興味はない React Vue Angular 7
  6. 特徴 • Virtual DOM(仮想)による直感的なUI実装 • コードとHTML要素をミックスして記述 • コンポーネント指向 • 豊富なライブラリやコンポーネントが存在

    • 自身のコンポーネントを簡単に作成 <div> <Row style={{ marginTop: "10px" }}> <Col style={{ float: "right", marginRight: "5px" }}> <Button type="primary" onClick={this.toggleNewTaskDialog} disabled={spininig} > {I18n.get("新しいタスク")} </Button> </Col> <Col style={{ float: "right", marginRight: "5px" }}> <Input placeholder={I18n.get("タスクのフィルター")} onChange={(e) => this.handleTaskFilterChange(e)} disabled={spininig} allowClear /> </Col> </Row> <Row style={{ marginTop: "2px" }}> { boards.map(board => { return ( <BoardLane key={board.id} board={board} taskFilter={this.state.taskFilter} {...this.props} /> ) }) } </Row> </div> タグとコードがミックス 8
  7. create-react-app • Front-endの技術スタックは組み合わせが複雑で管理が大変 • babel, webpack, browserify, jest, … •

    Facebook製のプロジェクト作成ツールを使えば簡単に作成可能 • create-react-app • npx • npm • yarn https://facebook.github.io/create-react-app/docs/getting-started 12 npx create-react-app my-app npm init react-app my-app yarn create react-app my-app
  8. コンポーネントのルール • Reactの2通りの定義方法がある • React.Componentを継承したクラスを作成する方法 • ライフサイクルやstateを持つ場合 • 純粋なrender関数として定義する方法 •

    どちらでもrender関数は1つのHTMLのDOM要素を返すことがルール になっている。 const Avatar = (props) => { const setting = ApplicationSetting.getServerSetting() const src = `${setting.url}/${setting.base}/${setting.img}/${props.avatar}${setting.ext}` const size = props.size || "50px" const alt = props.alt || "No Image" return ( <img src={src} width={size} height={size} borderstyle={"solid"} border={"thin solid gray"} align={"center"} alt={alt} /> ) } class MainPage extends Component { render() { return ( <MainPageTemplate {...this.props} /> ) } } 14
  9. propsとstate • コンポーネントは、以下の2つの状態を持つ。 • props … 親のコンポーネントから渡される読み取り専用の値 • 親から渡されるオブジェクトやコールバックなどを持つ。 •

    state … 自身のコンポーネントで持つ、編集可能な値 • チェックボックスや入力中のフィールド値などを持ち、setStateで更新する。 class MenuBar extends Component { constructor(props) { super(props) this.state = { showProfileDialog: false } this.toggleProfileDialog = this.toggleProfileDialog.bind(this) } render() { const loginUserName = this.props.loginState.loginUser.name || "" const avatarName = this.props.loginState.loginUser.avatar || "" return ( // ... 15
  10. 変数を使うには? • .jsxの中では、「{」と「}」で括って変数やプログラミング要素を記述する。 class LoginForm extends Component { constructor(props) {

    super(props) this.state = { name: "", password: "", showSignUpDialog: false, } this.handleTextChange = this.handleTextChange.bind(this) this.toggleSignUpDialog = this.toggleSignUpDialog.bind(this) } render() { const loginLabel = (!this.props.loginState.isLoginProcessing) ? I18n.get("ログイン") : I18n.get("ログイン中") const disabled = this.props.loginState.isLoginProcessing || this.state.name === "" return ( <div> <Col> <Row> <div style={{ fontSize: "32px" }}> <font color="cornflowerblue">Taskboard React</font> </div> </Row> // ... 16
  11. if文を使うには? • 「{」と「}」の中でif文を書く。 • 三項演算子を使う。 • (cond) ? <DOM/> :

    null である条件の時だけDOMを 表示する class ErrorCard extends Component { render() { const errorMessage = this.props.errorMessage const summary = errorMessage.summary const summaryColor = this.props.summaryColor || "red" const detailColor = this.props.detailColor || "gray" let detail = "" if (errorMessage.detail) { detail = (errorMessage.code) ? `${errorMessage.detail}(${errorMessage.code})` : `${errorMessage.detail}` } return ( <div style={this.props.cardStyle}> <div> <font color={summaryColor}> {summary} </font> </div> {(detail) ? <div> <font color={detailColor}> {detail} </font> </div> : null} </div> ) } } 17
  12. forループを使うには? • 配列要素で、render関数(DOM要素を 返す関数)を記述することができる。 class BoardLane extends Component { render()

    { return ( <Lane> <TaskList style={styles}> { tasks.map((task) => { return ( <TaskCard key={task.id} index={task.dispOrder} task={task} taskUser={userMap[task.assigneeUserId]} onEditButtonClick={this.props.onUpdateTaskButtonClick} onDeleteButtonClick={this.props.onDeleteTaskButtonClick} isSavingProcessing={this.props.tasksState.isSavingTask} users={this.props.usersState.users} boards={this.props.boardsState.boards} /> )}) } </TaskList> </Lane> ) } } tasks.map((task)=>{ return <DOM要素> }) 18
  13. 自身のコンポーネントの状態を変えるには? • this.setState関数でstateを変更することができる • setStateを呼び出すと再描画(renderの呼び出し)がされる class LoginForm extends Component {

    constructor(props) { super(props) this.state = { name: "", password: "", showSignUpDialog: false, } this.handleTextChange = this.handleTextChange.bind(this) this.toggleSignUpDialog = this.toggleSignUpDialog.bind(this) } ダイアログの表示フラグ 19 toggleSignUpDialog() { this.props.clearLoginErrors() this.setState({ showSignUpDialog: !this.state.showSignUpDialog, }) } フラグの切り替え
  14. 親のコンポーネントの状態を変えるには? • コールバックを呼び出すことで、reducerを通してpropsや親のstateを変 えることができる。 <Modal visible={this.state.showEditTaskDialog} onCancel={this.toggleEditTaskDialog} footer={null} destroyOnClose width={500}

    > <TaskForm mode={TaskForm.Mode.Edit} task={task} users={this.props.users} boards={this.props.boards} onSaveButtonClick={this.props.onEditButtonClick} isSavingProcessing={this.props.isSavingProcessing} /> </Modal> タスク表示 コンポーネント ダイアログのOKボタンで コンテナのタスク変更 関数が呼び出される 20
  15. Fluxアーキテクチャ • MVVMの双方向バイン ディングなどと異なり、 ビューモデルが「単一方 向」で更新されるアーキテ クチャ • 右はイメージ(gif) •

    View(コンポーネント)と Reducers(状態遷移)を 実装すればアプリの 画面が更新される 23 http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/27
  16. Redux – Store / Component • Store • アプリケーション全体の状態を表す •

    combineReducers を使って小さな状態の集まりに粒度化できる • Component • JSXで書かれたコンポーネント • 親から渡される読み取り専用のpropsと自身が持ち書き込み可能な stateを持つ 24
  17. Redux – Action / Reducers • Action / ActionCreater •

    UIやAPI呼び出しなどのイベント定義 • typeとpayloadと呼ばれるパラメータを持つ • Reducer • 状態を遷移させる純粋関数群 • 古い状態から新しい状態を作成する • Actionのtypeのcaseに応じて処理を書く • combineReducerというAPIで細粒度化可能 25 export const LOGIN_START_EVENT = "LOGIN_START_EVENT" export const loginStartEvent = (name, password) => ({ type: LOGIN_START_EVENT, payload: { name, password, } })
  18. Redux – Container • Container • コンポーネントと状態、Action の関数を接続させる役割を持 つ •

    mapStateToPropsでStoreの stateをマッピングし、 mapDispatchToPropsでAction イベント関数をdispatchする • mapStateToPropsと mapDispatchToPropsを connect関数で接続する 26 import { connect } from "react-redux" import { withRouter } from "react-router-dom" import LoginPage from "../components/pages/LoginPage" import { loginStartEvent, signUpStartEvent, clearLoginErrorsEvent, } from "../actions" const mapStateToProps = (state) => ({ loginState: state.loginState, }) const mapDispatchToProps = (dispatch) => ({ onLoginButtonClick: (name, password) => { dispatch(loginStartEvent(name, password)) }, onSignUpButtonClick: (userCreateRequest) => { dispatch(signUpStartEvent(userCreateRequest)) }, clearLoginErrors: () => { dispatch(clearLoginErrorsEvent()) }, }) const LoginContainer = connect( mapStateToProps, mapDispatchToProps, )(LoginPage) export default withRouter(LoginContainer) 画面からのcallback関数 とdispatchのマップ アプリケーション全 体の状態の一部と マップ
  19. タスク管理アプリの状態 • 以下の状態を定義&管理 • ログイン情報 • ボード一覧 • ユーザー一覧 •

    ボードごとのタスク一覧 • エラー情報 27 ログイン情報 ボード一覧 ボードごとの タスク一覧 ユーザー一覧 エラー情報
  20. Redux - storeのindex.js • combineReducersという関数で状態をマージ 28 import { combineReducers }

    from "redux" import loginState from "./loginState" import mainState from "./mainState" import boardsState from "./boardsState" import tasksState from "./tasksState" import usersState from "./usersState" import errorState from "./errorState" const appState = combineReducers({ loginState, mainState, boardsState, tasksState, usersState, errorState, }) export default appState ログインユーザーオブジェクト ログイン中フラグ ログアウト中フラグ ・・・ ボードID-タスク配列のマップ 新規タスク作成中状態 タスク移動中フラグ ・・・
  21. Redux-Saga • Redux-Saga とは、非同期処理を取 り扱うReduxのミドルウェアライブラリ • 非同期処理を手続型で記述すること が可能 • App.jsxで、applyMiddlewareを行う

    ことで利用可能 30 import React from "react" import { render } from "react-dom" import { Provider } from "react-redux" import { createStore, applyMiddleware } from "redux" import createSagaMiddleware from "Redux-Saga" import App from "./components/App" import appState from "./reducers" import rootSaga from "./sagas/saga" const sagaMiddleware = createSagaMiddleware() const store = createStore( appState, applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(rootSaga) render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") )
  22. Redux-Sagaの基本 • 非同期処理のイベントは、「Xxx開始」、「Xxx成功」、「Xxx失敗」の3つに分かれる • 画面イベントを受け取るジェネレーター関数を fork することでイベントを監視 • イベントは、takeEvery, call,

    putで処理される • takeEvery … イベントの待ち受け • call … 非同期処理の呼び出し • put … 次のイベントの実行 31 function* handleCreateTask() { yield takeEvery(CREATE_TASK_START_EVENT, createTask) } function* createTask(action) { const payload = action.payload const { task, error } = yield call( TaskService.createAsync, payload.task) if (!error) { yield put(createTaskSuccessEvent(task)) } else { yield put(createTaskFailureEvent(error)) } } 待ち受け 非同期呼び出し 次のイベント実行 export default function* rootSaga() { let sagaFunctions = [] sagaFunctions = sagaFunctions.concat(UserSagas.sagaFunctions()) sagaFunctions = sagaFunctions.concat(BoardSagas.sagaFunctions()) sagaFunctions = sagaFunctions.concat(TaskSagas.sagaFunctions()) for (let i = 0; i < sagaFunctions.length; i++) { yield fork(sagaFunctions[i]) } } イベントハンドラ (ジェネレータ関数)の登録 イベントハンドラ (ジェネレータ関数)
  23. 覚えていてほしいこと • React というUIのためのJSライブラリ • Virtual DOM = UI記述のための拡張JSと、コンポーネント指向という特徴を持つ •

    Redux … アプリケーションの状態管理のReactのためのライブラリ • Redux-saga … 非同期処理を扱うReduxのミドルウェア • create-react-app で簡単にアプリケーションの雛形が作れる 32