19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React+ReduxのチュートリアルTodoアプリの構築

Last updated at Posted at 2016-11-09

#概要
Reduxの公式ドキュメントにあるReact+Reduxで作るTodoアプリのソース、設定ファイルおよび環境のまとめです。
公式ドキュメントだと、初心者が「とりあえず動くところまで自分で確認したいんだけど。。。」を実現するのに不十分だと感じたので、改めてまとめます。

#執筆日
2016/11/10
上記日付以降は、Reduxの公式ドキュメントが変更されている可能性があり、ソース等はその分違う場合はご容赦ください。

#構築アプリ外観
こんな感じのアプリを作ります。
サンプル.png

#開発環境
飽くまで私の開発環境です。

OS:Windows10
node.js : v6.9.1
npm : 3.10.8
webpack : 1.13.2

node.jsがインストールされていなければ、node.jsの公式サイトでインストーラをダウンロードし、インストールしてください。

また、ビルドツール(コンパイラ)にはwebpackを利用するので、node.jsをインストールした後、下記のコマンドをコマンドプロンプト等で実行してwebpackをインストールしてください。
npm install webpack -g

#ソースプログラム構成
フォルダ・ファイル構成はこんな感じです。
([D:\projects\react_redux] をプロジェクトルートフォルダにしてます)
とりあえず、空のフォルダ・ファイルを作って、後に記述します内容で上書きすればい良いと思います。
構造.png

#手順概要

  1. ソースコード記述
  • 設定ファイル記述
  • 依存パッケージインストール(ダウンロード)
  • ビルド
  • ソース・ビルドファイルをサーバにアップロード

#手順詳細
##ソースコード記述

###action

src\actions\index.js
let nextTodoId = 0;

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

export const setVisibilityFilter = (filter) => {
    return {
        type: 'SET_VISIBILITY_FILTER',
        filter
    }
}

export const toggleTodo = (id) => {
    return {
        type: 'TOGGLE_TODO',
        id
    }
}

###component

src\components\App.js
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
    <div>
        <AddTodo />
        <VisibleTodoList />
        <Footer />
    </div>
)

export default App
src\components\Footer.js
import React from 'react'
import FilterLink from '../containers/FilterLink'

const Footer = () => (
    <p>
        Show:
        {" "}
        <FilterLink filter="SHOW_ALL">
            All
        </FilterLink>
        {", "}
        <FilterLink filter="SHOW_ACTIVE">
            Active
        </FilterLink>
        {", "}
        <FilterLink filter="SHOW_COMPLETED">
            Completed
        </FilterLink>
    </p>
)

export default Footer
src\components\Link.js
import React, { PropTypes } from 'react'

const Link = ({ active, children, onClick }) => {
    if (active) {
        return <span>{children}</span>
    }

    return (
        <a href="#"
           onClick={e => {
               e.preventDefault()
               onClick()
           }}
        >
            {children}
        </a>
    )
}

Link.propTypes = {
    active: PropTypes.bool.isRequired,
    children: PropTypes.node.isRequired,
    onClick: PropTypes.func.isRequired
}

export default Link
src\components\Todo.js
import React, { PropTypes } from 'react'

const Todo = ({ onClick, completed, text }) => (
    <li
        onClick={onClick}
        style={{
            textDecoration: completed ? 'line-through' : 'none'
        }}
    >
        {text}
    </li>
)

Todo.propTypes = {
    onClick: PropTypes.func.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
}
export default Todo
src\components\TodoList.js
import React, { PropTypes } from 'react'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
    <ul>
        {todos.map(todo =>
            <Todo
                key={todo.id}
                {...todo}
                onClick={() => onTodoClick(todo.id)}
            />
        )}
    </ul>
)

TodoList.propTypes = {
    todos: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        completed: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired
    }).isRequired).isRequired,
    onTodoClick: PropTypes.func.isRequired
}

export default TodoList

###container

src\containers\AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
    let input

    return (
        <div>
            <form onSubmit={e => {
                e.preventDefault()
                if (!input.value.trim()) {
                    return
                }
                dispatch(addTodo(input.value))
                input.value = ''
            }}>
                <input ref={node => {
                    input = node
                }} />
                <button type="submit">
                    Add Todo
                </button>
            </form>
        </div>
    )
}
AddTodo = connect()(AddTodo)

export default AddTodo
src\containers\FilterLink.js
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
    return {
        active: ownProps.filter === state.visibilityFilter
    }
}

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        onClick: () => {
            dispatch(setVisibilityFilter(ownProps.filter))
        }
    }
}

const FilterLink = connect(
    mapStateToProps,
    mapDispatchToProps
)(Link)

export default FilterLink
src\containers\VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
    switch (filter) {
        case 'SHOW_ALL':
            return todos
        case 'SHOW_COMPLETED':
            return todos.filter(t => t.completed)
        case 'SHOW_ACTIVE':
            return todos.filter(t => !t.completed)
    }
}

const mapStateToProps = (state) => {
    return {
        todos: getVisibleTodos(state.todos, state.visibilityFilter)
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        onTodoClick: (id) => {
            dispatch(toggleTodo(id))
        }
    }
}

const VisibleTodoList = connect(
    mapStateToProps,
    mapDispatchToProps
)(TodoList)

export default VisibleTodoList
src\containers\index.js
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

const todoApp = combineReducers({
    todos,
    visibilityFilter
})

export default todoApp
src\containers\todos.js
const todo = (state = {}, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                id: action.id,
                text: action.text,
                completed: false
            }
        case 'TOGGLE_TODO':
            if (state.id !== action.id) {
                return state
            }

            return Object.assign({}, state, {
                completed: !state.completed
            })

        default:
            return state
    }
};

const todos = (state = [], action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return [
                ...state,
                todo(undefined, action)
            ]
        case 'TOGGLE_TODO':
            return state.map(t =>
                todo(t, action)
            )
        default:
            return state
    }
}

export default todos
src\containers\visibilityFilter.js
const visibilityFilter = (state = 'SHOW_ALL', action) => {
    switch (action.type) {
        case 'SET_VISIBILITY_FILTER':
            return action.filter
        default:
            return state
    }
}

export default visibilityFilter

###reducer

src\reducers\index.js
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

const todoApp = combineReducers({
    todos,
    visibilityFilter
})

export default todoApp
src\reducers\todos.js
const todo = (state = {}, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                id: action.id,
                text: action.text,
                completed: false
            }
        case 'TOGGLE_TODO':
            if (state.id !== action.id) {
                return state
            }

            return Object.assign({}, state, {
                completed: !state.completed
            })

        default:
            return state
    }
};

const todos = (state = [], action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return [
                ...state,
                todo(undefined, action)
            ]
        case 'TOGGLE_TODO':
            return state.map(t =>
                todo(t, action)
            )
        default:
            return state
    }
}

export default todos
src\reducers\visibilityFilter.js
const visibilityFilter = (state = 'SHOW_ALL', action) => {
    switch (action.type) {
        case 'SET_VISIBILITY_FILTER':
            return action.filter
        default:
            return state
    }
}

export default visibilityFilter

###その他

src\index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)
index.html
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<div id="root"></div>
<script src="dist/index.js"></script>
</body>
</html>

##設定ファイル記述

package.json
{
  "name": "react_redux",
  "dependencies": {
    "react": "*",
    "react-dom": "*",
    "redux": "*",
    "react-redux": "*"
  },
  "devDependencies": {
    "babel-core": "*",
    "babel-loader": "*",
    "babel-preset-es2015": "*",
    "babel-preset-react": "*"
  }
}

webpack.config.js
module.exports = {
    context: __dirname + '/src',
    entry: {
        javascript: './index.js'
    },
    output: {
        path: __dirname + '/dist',
        filename: 'index.js'
    },
    resolve: {
        extensions: ['', '.js']
    },
    // devtool: "source-map",
    plugins: [],
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel',
                query: {
                    cacheDirectory: true,
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
}

##依存パッケージインストール(ダウンロード)
コマンドプロンプト等で下記コマンドを実行し、[package.json]に記述した依存パッケージをダウンロードします。

cd D:\projects\react_redux
npm install

すると、[D:\projects\react_redux\node_modules] フォルダが自動生成され、その配下にパッケージがダウンロードされます。

##ビルド
コマンドプロンプト等で下記コマンドを実行し、ビルドします。

cd D:\projects\react_redux
webpack

##ソース・ビルドファイルをサーバにアップロード
下記ファイルをWebサーバにアップロードします。

  • index.html
  • dist\index.js

ブラウザでアクセスして動けばOKです。

#以上
以上です。
仕組みは自分が理解できてないので、理解できたらまた書きます。

19
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?