Step by Step で学ぶ

react + redux

Motivation

- redux 難しい...

- connect 難しい...

徐々にredux を導入するサンプルがあれば、

分かりやすいのでは?

題材

メモを追加するだけアプリ

目次

1. redux 無し

2. redux 有り / react-redux無し

3. redux 有り/ react-redux有り

redux 無し

親のstateを使い回すパターン

App.js

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { todos: [] };

    this.addTodo = this.addTodo.bind(this);
  }

  addTodo(word) {
    let id = 0;
    this.setState({ todos: [...this.state.todos, { id, word }] });
    ++id;
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <Todo todos={this.state.todos} addTodo={this.addTodo} />
        </header>
      </div>
    );
  }
}

state を生成して、state.todo を <Todo> 渡す

Todo.js

const Todo = ({ todos, addTodo }) => {
  let input;

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault();
          if (!input.value.trim()) return;
          addTodo(input.value);
          input.value = "";
        }}
      >
        <input ref={node => (input = node)} />
        <button type="submit">Add Todo</button>
      </form>
      {todos.map((todo, index) => (
        <p key={index}>{todo.word}</p>
      ))}
    </div>
  );
};

state.todo を 描画

redux 有り /

react-redux無し

前回の問題

- state をバケツリレー

- (きっと今後) state 大量発生

今回の改善

- state を外に出して一元管理

App.js

const store = createStore(todoApp)

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {todos: []};
    store.subscribe(() => {
      this.setState({todos: store.getState().todos})
    });
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <Todo store={store} todos={this.state.todos} />
        </header>
      </div>
    );
  }
}

redux の createStore で store を作成

Todo.js

const Todo = ({ store, todos }) => {
  let input;

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault();
          if (!input.value.trim()) return;
          store.dispatch(addTodo(input.value))
          input.value = "";
        }}
      >
        <input ref={node => (input = node)} />
        <button type="submit">Add Todo</button>
      </form>
      {todos.map((todo, index) => (
        <p key={index}>{todo.text}</p>
      ))}
    </div>
  );
};

store.dispatch で addTodo を実行

Actions.js


let nextTodoId = 0
export const addTodo = text => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text
})

action を作る関数

Reducer.js

import { combineReducers } from 'redux'

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
        }
      ]
    default:
      return state
  }
}

export default combineReducers({
  todos,
})

store.dispatch で action を受ける

action.type に応じて store を更新する

redux 有り /

react-redux 有り

前回の問題

- store をバケツリレー

今回の改善

- 下のコンポーネントから直接に store を参照したい

- 下のコンポーネントから直接に action を参照したい

App.js

const store = createStore(todoApp)

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <Provider store={store}>
          <header className="App-header">
            <Todo />
          </header>
        </Provider>
      </div>
    );
  }
}

react-redux の Provider で

 store の変更を下のコンポーネントに伝える

Todo.js

const Todo = props => {
  const { todos, addTodo } = props;

  let input;

  return (
    <div>
      // 略
      {todos.map((todo, index) => (
        <p key={index}>{todo.text}</p>
      ))}
    </div>
  );
};

export default connect(
  state => ({
    todos: state.todos
  }),
  dispatch => ({
    addTodo: text => dispatch(addTodo(text))
  })
)(Todo);

react-redux の connent で

 store に直接アクセスする

Reducer.js

Action.js

 

前回と同じ!!

今回のまとめ

- redux は 一番の親のstate を外に切り出したもの(=store)

 

- react-redux は store をreact で「楽に」使えるようにするもの

おまけ (hooks)

前回の問題

- connect 書くのダルい

今回の改善

- connect 書かなくていい

const Todo = () => {
  const dispatch = useDispatch()

  const todos = useSelector(state => state.todos)
  const handleAddTodo = (word) => dispatch(addTodo(word))

  let input;

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault();
          if (!input.value.trim()) {
            return;
          }
          handleAddTodo(input.value);
          input.value = "";
        }}
      >
        <input ref={node => (input = node)} />
        <button type="submit">Add Todo</button>
      </form>
      {todos.map((todo, index) => (
        <p key={index}>{todo.text}</p>
      ))}
    </div>
  );
};

Todo.js

Made with Slides.com