├── examples ├── todo │ └── .gitkeep ├── utils │ ├── domReady.js │ └── BaseComponent.js ├── counter │ ├── js │ │ ├── reducers │ │ │ ├── Root.js │ │ │ └── CounterReducer.js │ │ ├── actions │ │ │ └── ActionCreator.js │ │ ├── test │ │ │ ├── actions │ │ │ │ └── actions.spec.js │ │ │ ├── .eslintrc │ │ │ └── reducers │ │ │ │ └── CounterReducer.spec.js │ │ ├── index.js │ │ └── components │ │ │ └── Counter.js │ └── index.html ├── greeting │ ├── js │ │ ├── actions │ │ │ └── ActionCreator.js │ │ ├── reducers │ │ │ ├── Root.js │ │ │ └── GreetingReducer.js │ │ ├── test │ │ │ ├── actions │ │ │ │ └── actions.spec.js │ │ │ ├── .eslintrc │ │ │ └── reducers │ │ │ │ └── GreetingReducer.spec.js │ │ ├── index.js │ │ └── components │ │ │ └── Greeting.js │ └── index.html ├── weather │ ├── js │ │ ├── reducers │ │ │ ├── Root.js │ │ │ └── WeatherReducer.js │ │ ├── index.js │ │ ├── test │ │ │ ├── .eslintrc │ │ │ └── reducers │ │ │ │ └── WeatherReducer.spec.js │ │ ├── actions │ │ │ └── ActionCreator.js │ │ └── components │ │ │ └── Weather.js │ └── index.html └── todo-answer │ ├── js │ ├── contents │ │ └── contents.js │ ├── reducers │ │ ├── Root.js │ │ ├── TabReducer.js │ │ └── TodoReducer.js │ ├── index.js │ ├── test │ │ ├── .eslintrc │ │ ├── reducers │ │ │ ├── TabReducer.spec.js │ │ │ └── TodoReducer.spec.js │ │ └── actions │ │ │ └── actins.spec.js │ ├── actions │ │ └── ActionCreator.js │ └── components │ │ └── Todo.js │ ├── index.html │ └── css │ └── style.css ├── .babelrc ├── .textlintrc ├── .gitignore ├── book.json ├── docs ├── img │ ├── redux-view.jpg │ └── redux_arch.png ├── examples │ ├── README.md │ ├── weather.md │ ├── greeting.md │ └── counter.md ├── todo.md ├── redux.md ├── redux_middleware.md └── es6.md ├── .editorconfig ├── SUMMARY.md ├── index.html ├── README.md ├── package.json └── .eslintrc /examples/todo/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.textlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | } 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | _book 4 | .idea/ -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "include-codeblock" 4 | ] 5 | } -------------------------------------------------------------------------------- /docs/img/redux-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkamakura/redux-jquery/HEAD/docs/img/redux-view.jpg -------------------------------------------------------------------------------- /docs/img/redux_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkamakura/redux-jquery/HEAD/docs/img/redux_arch.png -------------------------------------------------------------------------------- /examples/utils/domReady.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | export default (handler) => $(document).ready(handler); 4 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | 3 | * Examples 4 | * [Counter](counter.md) 5 | * [Greeting](greeting.md) 6 | * [Weather](weather.md) 7 | -------------------------------------------------------------------------------- /examples/counter/js/reducers/Root.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { CounterReducer } from './CounterReducer'; 3 | 4 | export const RootReducer = combineReducers({ 5 | result: CounterReducer 6 | }); 7 | -------------------------------------------------------------------------------- /examples/greeting/js/actions/ActionCreator.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | export const UPDATE_NAME = 'UPDATE_NAME'; 4 | 5 | export const updateName = createAction(UPDATE_NAME, (name) => name); 6 | -------------------------------------------------------------------------------- /examples/weather/js/reducers/Root.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { WeatherReducer } from './WeatherReducer'; 3 | 4 | export const RootReducer = combineReducers({ 5 | weather: WeatherReducer 6 | }); 7 | -------------------------------------------------------------------------------- /examples/greeting/js/reducers/Root.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { GreetingReducer } from './GreetingReducer'; 3 | 4 | export const RootReducer = combineReducers({ 5 | name: GreetingReducer 6 | }); 7 | -------------------------------------------------------------------------------- /examples/todo-answer/js/contents/contents.js: -------------------------------------------------------------------------------- 1 | export const TODO_STATUS = { 2 | ACTIVE: 'ACTIVE', 3 | COMPLETED: 'COMPLETED' 4 | }; 5 | 6 | export const TAB = { 7 | ALL: 'ALL', 8 | ACTIVE: 'ACTIVE', 9 | COMPLETED: 'COMPLETED' 10 | }; 11 | -------------------------------------------------------------------------------- /examples/todo-answer/js/reducers/Root.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { TodoReducer } from './TodoReducer'; 3 | import { TabReducer } from './TabReducer'; 4 | 5 | export const RootReducer = combineReducers({ 6 | tab: TabReducer, 7 | todo: TodoReducer 8 | }); 9 | -------------------------------------------------------------------------------- /examples/counter/js/actions/ActionCreator.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | export const INCREMENT = 'INCREMENT'; 4 | export const DECREMENT = 'DECREMENT'; 5 | 6 | export const increment = createAction(INCREMENT); 7 | export const decrement = createAction(DECREMENT); 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{html,css,js,md}] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | insert_final_newline = true 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | indent_size = 4 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /examples/greeting/js/reducers/GreetingReducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { UPDATE_NAME } from '../actions/ActionCreator'; 3 | 4 | const initialState = ''; 5 | 6 | export const GreetingReducer = handleActions({ 7 | [UPDATE_NAME]: (state, action) => action.payload 8 | }, initialState); 9 | -------------------------------------------------------------------------------- /examples/greeting/js/test/actions/actions.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as actions from '../../actions/ActionCreator'; 3 | 4 | test('updateName should create updateName action', (t) => { 5 | t.deepEqual(actions.updateName('Kamakura Masaya'), {type: actions.UPDATE_NAME, payload: 'Kamakura Masaya'}); 6 | }); 7 | -------------------------------------------------------------------------------- /examples/counter/js/reducers/CounterReducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { INCREMENT, DECREMENT } from '../actions/ActionCreator'; 3 | 4 | const initialState = 0; 5 | 6 | export const CounterReducer = handleActions({ 7 | [INCREMENT]: (state) => state + 1, 8 | [DECREMENT]: (state) => state - 1 9 | }, initialState); 10 | -------------------------------------------------------------------------------- /docs/examples/weather.md: -------------------------------------------------------------------------------- 1 | # Weather 2 | 3 | ## はじめに 4 | サーバーから非同期でデータを取得して画面に反映する処理の説明をします。非同期処理以外の処理については`Counter`や`Greeting`の説明を参照してください。 5 | Reduxで非同期処理を扱う場合は`redux-thunk`というミドルウェアが使われます。 6 | 7 | ## Action 8 | 9 | [import](../../examples/weather/js/actions/ActionCreator.js) 10 | 11 | `updateWeather()`で非同期を行い、結果によって正常系`action`と異常系`action`を`dispatch`している。 -------------------------------------------------------------------------------- /examples/todo-answer/js/reducers/TabReducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | 3 | import { CHANGE_TAB } from '../actions/ActionCreator'; 4 | import { TAB } from '../contents/contents'; 5 | 6 | const initialState = TAB.ACTIVE; 7 | 8 | export const TabReducer = handleActions({ 9 | [CHANGE_TAB]: (state, action) => TAB[action.payload] 10 | }, initialState); 11 | -------------------------------------------------------------------------------- /examples/counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux-Counter 6 | 7 | 8 |
9 | 10 | 11 | 0 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Read Me](README.md) 4 | * [Redux Overview](docs/redux.md) 5 | * [Redux Middleware](docs/redux_middleware.md) 6 | * [ES2015 First Step](docs/es6.md) 7 | * [Examples](docs/examples/README.md) 8 | * [Counter](docs/examples/counter.md) 9 | * [Greeting](docs/examples/greeting.md) 10 | * [Weather](docs/examples/weather.md) 11 | * Exercise 12 | * [TODO](docs/todo.md) 13 | 14 | -------------------------------------------------------------------------------- /examples/greeting/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux-Greeting 6 | 7 | 8 |
9 | 10 | 11 |

12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/counter/js/test/actions/actions.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as actions from '../../actions/ActionCreator'; 3 | 4 | test('increment should create increment action', (t) => { 5 | t.deepEqual(actions.increment(0), {type: actions.INCREMENT, payload: 0}); 6 | }); 7 | 8 | test('increment should create decrement action', (t) => { 9 | t.deepEqual(actions.decrement(0), {type: actions.DECREMENT, payload: 0}); 10 | }); 11 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## はじめに 4 | `Examples`で学習したことの確認のために、TODOアプリを実装してみましょう。`examples`ディレクトリに`todo`ディレクトリがあるので、ここに実装してください。 5 | 6 | ## 機能要件 7 | - テキストエリアにタスクを入力し、`Add`ボタンを押すとタスクを追加する 8 | - タスクは`Active`と`Complete`の状態を持ち、タスクリストから状態を変更できる 9 | - タスクリストは`ALL`,`Active`と`Complete`の3パターン表示できる 10 | - タスクの削除はタスクリストから実行できる 11 | - 自分で機能を考えて追加しましょう 12 | 13 | 実装済みのサンプルは`examples/todo-answer`にあります。実装が終わって確認したいときや、困ったときに参考ください。またバグ報告/FIXや機能追加のプルリクエストはご気軽にお願いします。 -------------------------------------------------------------------------------- /examples/counter/js/index.js: -------------------------------------------------------------------------------- 1 | import domReady from '../../utils/domReady'; 2 | 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import createLogger from 'redux-logger'; 5 | 6 | import { RootReducer } from './reducers/Root'; 7 | import Counter from './components/Counter'; 8 | 9 | const createStoreWithMiddleware = applyMiddleware(createLogger())(createStore); 10 | const store = createStoreWithMiddleware(RootReducer); 11 | 12 | domReady(() => { 13 | new Counter('.counter', store); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/todo-answer/js/index.js: -------------------------------------------------------------------------------- 1 | import domReady from '../../utils/domReady'; 2 | 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import createLogger from 'redux-logger'; 5 | 6 | import { RootReducer } from './reducers/Root'; 7 | import Todo from './components/Todo'; 8 | 9 | const createStoreWithMiddleware = applyMiddleware(createLogger())(createStore); 10 | const store = createStoreWithMiddleware(RootReducer); 11 | 12 | domReady(() => { 13 | new Todo('.js-l-body', store); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/greeting/js/index.js: -------------------------------------------------------------------------------- 1 | import domReady from '../../utils/domReady'; 2 | 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import createLogger from 'redux-logger'; 5 | 6 | import { RootReducer } from './reducers/Root'; 7 | import Greeting from './components/Greeting'; 8 | 9 | const createStoreWithMiddleware = applyMiddleware(createLogger())(createStore); 10 | const store = createStoreWithMiddleware(RootReducer); 11 | 12 | domReady(() => { 13 | new Greeting('.greeting', store); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/weather/js/reducers/WeatherReducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { UPDATE_WEATHER, FETCH_EXCEPTION } from '../actions/ActionCreator'; 3 | 4 | const initialState = { 5 | data: [], 6 | message: '' 7 | }; 8 | 9 | export const WeatherReducer = handleActions({ 10 | [UPDATE_WEATHER]: (state, action) => Object.assign({}, state, { data: action.payload, message: '' }), 11 | [FETCH_EXCEPTION]: (state, action) => Object.assign({}, state, { message: action.payload }) 12 | }, initialState); 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index 6 | 7 | 8 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/weather/js/index.js: -------------------------------------------------------------------------------- 1 | import domReady from '../../utils/domReady'; 2 | 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import thunk from 'redux-thunk'; 5 | import createLogger from 'redux-logger'; 6 | 7 | import { RootReducer } from './reducers/Root'; 8 | import Weather from './components/Weather'; 9 | 10 | const createStoreWithMiddleware = applyMiddleware(createLogger(), thunk)(createStore); 11 | const store = createStoreWithMiddleware(RootReducer); 12 | 13 | domReady(() => { 14 | new Weather('.weather', store); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/counter/js/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["babel", "ava"], 3 | "rules": { 4 | "new-cap": 0, 5 | "babel/new-cap": 0, 6 | "babel/arrow-parens": 0, 7 | "ava/no-cb-test": 2, 8 | "ava/no-identical-title": 2, 9 | "ava/no-invalid-end": 2, 10 | "ava/no-only-test": 2, 11 | "ava/no-skip-assert": 2, 12 | "ava/no-skip-test": 2, 13 | "ava/no-todo-test": 1, 14 | "ava/prefer-power-assert": 2, 15 | "ava/test-ended": 2, 16 | "ava/test-title": [2, "always"], 17 | "ava/use-t": 2, 18 | "ava/use-test": 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/weather/js/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["babel", "ava"], 3 | "rules": { 4 | "new-cap": 0, 5 | "babel/new-cap": 0, 6 | "babel/arrow-parens": 0, 7 | "ava/no-cb-test": 2, 8 | "ava/no-identical-title": 2, 9 | "ava/no-invalid-end": 2, 10 | "ava/no-only-test": 2, 11 | "ava/no-skip-assert": 2, 12 | "ava/no-skip-test": 2, 13 | "ava/no-todo-test": 1, 14 | "ava/prefer-power-assert": 2, 15 | "ava/test-ended": 2, 16 | "ava/test-title": [2, "always"], 17 | "ava/use-t": 2, 18 | "ava/use-test": 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/greeting/js/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["babel", "ava"], 3 | "rules": { 4 | "new-cap": 0, 5 | "babel/new-cap": 0, 6 | "babel/arrow-parens": 0, 7 | "ava/no-cb-test": 2, 8 | "ava/no-identical-title": 2, 9 | "ava/no-invalid-end": 2, 10 | "ava/no-only-test": 2, 11 | "ava/no-skip-assert": 2, 12 | "ava/no-skip-test": 2, 13 | "ava/no-todo-test": 1, 14 | "ava/prefer-power-assert": 2, 15 | "ava/test-ended": 2, 16 | "ava/test-title": [2, "always"], 17 | "ava/use-t": 2, 18 | "ava/use-test": 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/todo-answer/js/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["babel", "ava"], 3 | "rules": { 4 | "new-cap": 0, 5 | "babel/new-cap": 0, 6 | "babel/arrow-parens": 0, 7 | "ava/no-cb-test": 2, 8 | "ava/no-identical-title": 2, 9 | "ava/no-invalid-end": 2, 10 | "ava/no-only-test": 2, 11 | "ava/no-skip-assert": 2, 12 | "ava/no-skip-test": 2, 13 | "ava/no-todo-test": 1, 14 | "ava/prefer-power-assert": 2, 15 | "ava/test-ended": 2, 16 | "ava/test-title": [2, "always"], 17 | "ava/use-t": 2, 18 | "ava/use-test": 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/greeting/js/test/reducers/GreetingReducer.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {GreetingReducer} from '../../reducers/GreetingReducer'; 3 | 4 | test('should provide the initial state', (t) => { 5 | t.truthy(GreetingReducer(undefined, {}) === ''); 6 | }); 7 | 8 | test('should handle UPDATE_NAME action', (t) => { 9 | t.truthy(GreetingReducer('', {type: 'UPDATE_NAME', payload: 'Masaya Kamakura'}) === 'Masaya Kamakura'); 10 | }); 11 | 12 | test('should handle unknown actions', (t) => { 13 | t.truthy(GreetingReducer('Masaya Kamakura', {type: 'unknown'}) === 'Masaya Kamakura'); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/todo-answer/js/test/reducers/TabReducer.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import {TabReducer} from '../../reducers/TabReducer'; 4 | import {TAB} from '../../contents/contents'; 5 | 6 | test('should provide the initial state', (t) => { 7 | t.truthy(TabReducer(undefined, {}) === TAB.ACTIVE); 8 | }); 9 | 10 | test('should handle CHANGE_TAB action', (t) => { 11 | t.truthy(TabReducer(TAB.ACTIVE, {type: 'CHANGE_TAB', payload: TAB.COMPLETED}) === TAB.COMPLETED); 12 | }); 13 | 14 | test('should handle unknown actions', (t) => { 15 | t.truthy(TabReducer(TAB.ACTIVE, {type: 'unknown'}) === TAB.ACTIVE); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/counter/js/test/reducers/CounterReducer.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {CounterReducer} from '../../reducers/CounterReducer'; 3 | 4 | test('should provide the initial state', (t) => { 5 | t.truthy(CounterReducer(undefined, {}) === 0); 6 | }); 7 | 8 | test('should handle INCREMENT action', (t) => { 9 | t.truthy(CounterReducer(1, {type: 'INCREMENT'}) === 2); 10 | }); 11 | 12 | test('should handle DECREMENT action', (t) => { 13 | t.truthy(CounterReducer(1, {type: 'DECREMENT'}) === 0); 14 | }); 15 | 16 | test('should handle unknown actions', (t) => { 17 | t.truthy(CounterReducer(1, {type: 'unknown'}) === 1); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/counter/js/components/Counter.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../actions/ActionCreator'; 2 | import BaseComponent from '../../../utils/BaseComponent'; 3 | 4 | export default class Counter extends BaseComponent { 5 | constructor(selector, store) { 6 | super(selector, store, 'result'); 7 | 8 | this.$result = this.$selector.find('.js-result'); 9 | this.$selector.find('.js-increment').on('click', () => this.dispatch(actions.increment())); 10 | this.$selector.find('.js-decrement').on('click', () => this.dispatch(actions.decrement())); 11 | } 12 | 13 | render() { 14 | this.$result.text(this.state.result); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/greeting/js/components/Greeting.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../actions/ActionCreator'; 2 | import BaseComponent from '../../../utils/BaseComponent'; 3 | 4 | export default class Greeting extends BaseComponent { 5 | constructor(selector, store) { 6 | super(selector, store, 'name'); 7 | 8 | this.$greeting = this.$selector.find('.js-greeting'); 9 | this.$inputName = this.$selector.find('input[name=name]'); 10 | this.$selector.find('.submit').on('click', () => this.dispatch(actions.updateName(this.$inputName.val()))); 11 | } 12 | 13 | render() { 14 | this.$greeting.text(`Hello, ${this.state.name}!!`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-jquery 2 | - Redux+jQuery examples 3 | - ES2015(babel) 4 | - **Examples don't use React** 5 | 6 | ## build 7 | ``` 8 | git clone https://github.com/mkamakura/redux-jquery.git 9 | cd redux-jquery && npm i 10 | npm start 11 | ``` 12 | Launch local server and watchify. 13 | 14 | ## Contribution 15 | 1. Fork it! 16 | 1. Create your feature branch: `git checkout -b my-new-feature` 17 | 1. Commit your changes: `git commit -am 'Add some feature'` 18 | 1. Push to the branch: `git push origin my-new-feature` 19 | 1. Submit a pull request 20 | 21 | ## GitBook 22 | - Japanese: https://www.gitbook.com/book/mkamakura/redux-jquery/details 23 | - Vietnamese: https://www.gitbook.com/book/mkamakura/redux-jquery-vn/details 24 | -------------------------------------------------------------------------------- /examples/weather/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux-weather 6 | 7 | 8 |
9 | 10 |

How is the weather in Tokyo now.

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Temperature
Max Temperature
Min Temperature
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/examples/greeting.md: -------------------------------------------------------------------------------- 1 | # Greeting 2 | 3 | ## はじめに 4 | 今回のサンプルでは`store.dispatch()`で画面のデータを渡し、そのデータを`state`にする処理を説明します。`Counter`との差分のみの説明になるので、わからないところは`Counter`の説明を参照してください。 5 | 6 | ## Component 7 | ```js 8 | this.$inputName = this.$selector.find('input[name=name]'); 9 | this.$selector.find('.submit').on('click', () => this.dispatch(actions.updateName(this.$inputName.val()))); 10 | ``` 11 | 12 | `ActionCreator`の引数にデータを入れることにより、データを渡すことができる。 13 | 14 | ## Action 15 | 16 | ```js 17 | export const UPDATE_NAME = 'UPDATE_NAME'; 18 | 19 | export const updateName = createAction(UPDATE_NAME, (name) => name); 20 | ``` 21 | `createAction`で`Reducer`に`name`を渡す。 22 | 23 | ## Reducer 24 | 25 | [import:6-8](../../examples/greeting/js/reducers/GreetingReducer.js) 26 | 27 | `ActionCreator`から受け取った値は`action.payload`オブジェクトにある。 -------------------------------------------------------------------------------- /examples/weather/js/test/reducers/WeatherReducer.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {WeatherReducer} from '../../reducers/WeatherReducer'; 3 | 4 | test('should provide the initial state', (t) => { 5 | t.deepEqual(WeatherReducer(undefined, {}), {data: [], message: ''}); 6 | }); 7 | 8 | test('should handle UPDATE_WEATHER action', (t) => { 9 | t.deepEqual(WeatherReducer({}, {type: 'UPDATE_WEATHER', payload: ['weather']}), {data: ['weather'], message: ''}); 10 | }); 11 | 12 | test('should handle FETCH_EXCEPTION action', (t) => { 13 | t.deepEqual(WeatherReducer({}, {type: 'FETCH_EXCEPTION', payload: 'exception'}), {message: 'exception'}); 14 | }); 15 | 16 | test('should handle unknown actions', (t) => { 17 | t.deepEqual(WeatherReducer({data: [], message: ''}, {type: 'unknown'}), {data: [], message: ''}); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/weather/js/actions/ActionCreator.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import { createAction } from 'redux-actions'; 3 | 4 | export const UPDATE_WEATHER = 'UPDATE_WEATHER'; 5 | export const FETCH_EXCEPTION = 'FETCH_EXCEPTION'; 6 | 7 | const WEATHER_API = 'http://api.openweathermap.org/data/2.5/weather?q=Tokyo,jp&APPID='; 8 | const API_KEY = '329465fbebeb9beb21a9d76142de6ce8'; 9 | 10 | const receiveWeather = createAction(UPDATE_WEATHER, (weather) => weather); 11 | const fetchException = createAction(FETCH_EXCEPTION, (message) => message); 12 | 13 | export function updateWeather() { 14 | return (dispatch) => { 15 | return fetch(`${WEATHER_API}${API_KEY}`) 16 | .then(response => response.json()) 17 | .then(json => dispatch(receiveWeather(json))) 18 | .catch(() => dispatch(fetchException('failed get weather information.'))); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /examples/todo-answer/js/actions/ActionCreator.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | export const INITIAL_TODO = 'INITIAL_TODO'; 4 | export const ADD_TODO = 'ADD_TODO'; 5 | export const DELETE_TODO = 'DELETE_TODO'; 6 | export const COMPLETE_TODO = 'COMPLETE_TODO'; 7 | export const ACTIVE_TODO = 'ACTIVE_TODO'; 8 | export const CHANGE_TAB = 'CHANGE_TAB'; 9 | export const CANCEL = 'CANCEL'; 10 | 11 | export const initialTodo = createAction(INITIAL_TODO, (todos) => todos); 12 | export const deleteTodo = createAction(DELETE_TODO, (id) => parseInt(id)); 13 | export const completeTodo = createAction(COMPLETE_TODO, (id) => parseInt(id)); 14 | export const activeTodo = createAction(ACTIVE_TODO, (id) => parseInt(id)); 15 | export const changeTab = createAction(CHANGE_TAB, (tab) => tab); 16 | 17 | export function addTodo(text) { 18 | return text === '' ? createAction(CANCEL)() : createAction(ADD_TODO)({ text }); 19 | } 20 | -------------------------------------------------------------------------------- /examples/utils/BaseComponent.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import is from 'predicates'; 3 | 4 | export default class BaseComponent { 5 | constructor(selector, store, ...stateNames) { 6 | this.$selector = $(selector); 7 | this.state = store.getState(); 8 | this.dispatch = store.dispatch; 9 | store.subscribe(() => { 10 | const newState = store.getState(); 11 | if (is.empty(stateNames)) { 12 | this.preRender(newState); 13 | return; 14 | } 15 | if (!is.empty(stateNames.filter((name) => this.preRender(newState, name)))) this.render(); 16 | }); 17 | } 18 | 19 | preRender(newState, name = null) { 20 | if (name === null && this.state !== newState) { 21 | this.state = newState; 22 | this.render(); 23 | return false; 24 | } 25 | 26 | if (this.state[name] !== newState[name]) { 27 | this.state[name] = newState[name]; 28 | return true; 29 | } 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/weather/js/components/Weather.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../actions/ActionCreator'; 2 | import BaseComponent from '../../../utils/BaseComponent'; 3 | import is from 'predicates'; 4 | 5 | export default class Weather extends BaseComponent { 6 | constructor(selector, store) { 7 | super(selector, store, 'weather'); 8 | 9 | this.$temp = this.$selector.find('.js-temp'); 10 | this.$tempMax = this.$selector.find('.js-temp-max'); 11 | this.$tempMin = this.$selector.find('.js-temp-min'); 12 | this.$errorMessage = this.$selector.find('.js-error-message'); 13 | this.$selector.find('.update').on('click', () => this.dispatch(actions.updateWeather())); 14 | } 15 | 16 | render() { 17 | if (is.notEmpty(this.state.weather.message)) { 18 | this.$errorMessage.text(this.state.weather.message); 19 | return; 20 | } 21 | 22 | if (is.notEmpty(this.state.weather.data)) { 23 | this.$temp.text(this.convertKtoC(this.state.weather.data.main.temp)); 24 | this.$tempMax.text(this.convertKtoC(this.state.weather.data.main.temp_max)); 25 | this.$tempMin.text(this.convertKtoC(this.state.weather.data.main.temp_min)); 26 | } 27 | } 28 | 29 | convertKtoC(temp) { 30 | return `${Math.round(temp - 273.15)}℃`; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/todo-answer/js/test/actions/actins.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {TAB, TODO_STATUS} from '../../contents/contents'; 3 | import * as actions from '../../actions/ActionCreator'; 4 | 5 | test('initialTodo should create initialTodo action', (t) => { 6 | t.deepEqual(actions.initialTodo({id: 1, text: 'todo', status: TODO_STATUS.ACTIVE}), {type: actions.INITIAL_TODO, payload: {id: 1, text: 'todo', status: TODO_STATUS.ACTIVE}}); 7 | }); 8 | 9 | test('deleteTodo should create deleteTab action', (t) => { 10 | t.deepEqual(actions.deleteTodo('1'), {type: actions.DELETE_TODO, payload: 1}); 11 | }); 12 | 13 | test('completeTodo should create completeTodo action', (t) => { 14 | t.deepEqual(actions.completeTodo('1'), {type: actions.COMPLETE_TODO, payload: 1}); 15 | }); 16 | 17 | test('activeTodo should create activeTodo action', (t) => { 18 | t.deepEqual(actions.activeTodo('1'), {type: actions.ACTIVE_TODO, payload: 1}); 19 | }); 20 | 21 | test('changeTab should create changeTab action', (t) => { 22 | t.deepEqual(actions.changeTab(TAB.ALL), {type: actions.CHANGE_TAB, payload: TAB.ALL}); 23 | t.deepEqual(actions.changeTab(TAB.ACTIVE), {type: actions.CHANGE_TAB, payload: TAB.ACTIVE}); 24 | t.deepEqual(actions.changeTab(TAB.COMPLETED), {type: actions.CHANGE_TAB, payload: TAB.COMPLETED}); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/todo-answer/js/reducers/TodoReducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { INITIAL_TODO, ADD_TODO, DELETE_TODO, COMPLETE_TODO, ACTIVE_TODO, CANCEL } from '../actions/ActionCreator'; 3 | import { TODO_STATUS } from '../contents/contents'; 4 | 5 | const initialState = [{ 6 | id: 0, 7 | text: 'todo', 8 | status: TODO_STATUS.ACTIVE 9 | }]; 10 | 11 | export const TodoReducer = handleActions({ 12 | [INITIAL_TODO]: (state, action) => { 13 | return action.payload.map((todo) => { 14 | return {id: todo.id, text: todo.text, status: TODO_STATUS[todo.status]}; 15 | }); 16 | }, 17 | [ADD_TODO]: (state, action) => [ 18 | ...state, 19 | { 20 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, 21 | text: action.payload.text, 22 | status: TODO_STATUS.ACTIVE 23 | } 24 | ], 25 | [DELETE_TODO]: (state, action) => state.filter((todo) => todo.id !== action.payload), 26 | [COMPLETE_TODO]: (state, action) => state.map( 27 | (todo) => todo.id === action.payload ? 28 | Object.assign({}, todo, {status: TODO_STATUS.COMPLETED}) : 29 | todo 30 | ), 31 | [ACTIVE_TODO]: (state, action) => state.map( 32 | (todo) => todo.id === action.payload ? 33 | Object.assign({}, todo, {status: TODO_STATUS.ACTIVE}) : 34 | todo 35 | ), 36 | [CANCEL]: (state) => state 37 | }, initialState); 38 | -------------------------------------------------------------------------------- /examples/todo-answer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redux-TODO 8 | 9 | 10 |
11 |

TODO

12 | 13 |
14 | 15 |
ADD
16 |
17 | 18 |
19 |
ALL
20 |
ACTIVE
21 |
COMPLETED
22 |
23 | 24 |
25 |
26 |
27 |
28 | 29 | 33 |
34 | 35 | 36 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-jquery", 3 | "version": "0.0.1", 4 | "description": "jQuery/Redux Examples", 5 | "main": "index.js", 6 | "scripts": { 7 | "test:ava": "ava -v", 8 | "test:eslint": "eslint examples", 9 | "test:textlint": "textlint --preset preset-japanese docs", 10 | "test": "run-p -l test:*", 11 | "build:gitbook": "gitbook build", 12 | "serve:gitbook": "gitbook serve", 13 | "serve:server": "browser-sync start --server --files **/*", 14 | "start": "run-p -l watch serve:server", 15 | "build": "globar \"examples/**/js/index.js\" --outfile=build", 16 | "watch": "globar \"examples/**/js/index.js\" --outfile=build -d -w" 17 | }, 18 | "author": "Masaya Kamakura (https://github.com/mkamakura)", 19 | "license": "MIT", 20 | "dependencies": { 21 | "isomorphic-fetch": "2.2.1", 22 | "jquery": "2.2.4", 23 | "predicates": "1.0.1", 24 | "redux": "3.5.2", 25 | "redux-actions": "0.9.1", 26 | "redux-logger": "2.6.1", 27 | "redux-thunk": "2.1.0" 28 | }, 29 | "devDependencies": { 30 | "ava": "0.15.2", 31 | "babel": "6.5.2", 32 | "babel-eslint": "6.0.4", 33 | "babelify": "7.3.0", 34 | "browser-sync": "2.12.12", 35 | "eslint": "2.11.1", 36 | "eslint-plugin-ava": "2.4.0", 37 | "eslint-plugin-babel": "3.2.0", 38 | "gitbook": "3.0.3", 39 | "gitbook-cli": "2.2.0", 40 | "gitbook-plugin-include-codeblock": "1.9.0", 41 | "globar": "0.9.7", 42 | "npm-run-all": "2.1.1", 43 | "textlint": "6.10.0", 44 | "textlint-rule-preset-japanese": "1.3.2" 45 | }, 46 | "engines": { 47 | "node": ">=5.0.0" 48 | }, 49 | "browserify": { 50 | "transform": [ 51 | [ 52 | "babelify", 53 | { 54 | "sourceMap": true 55 | } 56 | ] 57 | ] 58 | }, 59 | "ava": { 60 | "files": [ 61 | "examples/**/*.spec.js" 62 | ], 63 | "require": [ 64 | "babel-core/register" 65 | ], 66 | "babel": "inherit" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/examples/counter.md: -------------------------------------------------------------------------------- 1 | # Counter 2 | 3 | ## はじめに 4 | 今回のサンプルではデータフローを理解することに重点を置いて説明します。 5 | 実際にサンプルコードを写経しながら説明を読むと、理解が深まると思います。 6 | 7 | イベント発生後のコードを読んでいく順番は以下のとおりです。これを念頭に置いてはじめましょう。 8 | 9 | **`store.dispatch()`->`Action`->`Reducer`->`Componet:render()`** 10 | 11 | ## Component 12 | 13 | ### BaseComponent 14 | - すべての`Component`は`BaseComponent`を継承する前提でつくられます 15 | 16 | [import](../../examples/utils/BaseComponent.js) 17 | 18 | `constructor` 19 | ```js 20 | constructor(selector, store, ...stateNames) 21 | ``` 22 | - `selector`: `Component`のDOMのクラス名またはID 23 | - `store`: `rootReducer` 24 | - `...stateNames`: 監視する`state`(文字列) 25 | 26 | - `...stateNames`で指定した`state`に変更があったら子クラスで実装した`render()`を実行する機能を提供します 27 | 28 | ### constructor() 29 | **役割** 30 | - イベントハンドラのセット 31 | - `render()`で使用するセレクタがある場合は定義しておく 32 | 33 | ```js 34 | this.$result = this.$selector.find('.js-result'); 35 | this.$selector.find('.js-increment').on('click', () => this.dispatch(actions.increment())); 36 | this.$selector.find('.js-decrement').on('click', () => this.dispatch(actions.decrement())); 37 | ``` 38 | 39 | `super(selector, store, 'result');`は、 `state`の`result`に変更があったら`render()`を実行するように定義している。 40 | イベントハンドラのセットでは`click`イベントが発生したら`store.dispatch()`を実行するようにしている。 41 | 42 | ### render() 43 | **役割** 44 | - `state`の変更に伴う画面の再描画を行う 45 | 46 | ``` 47 | render() { 48 | this.$result.text(this.state.result); 49 | } 50 | ``` 51 | 52 | ## Action 53 | **役割** 54 | - `Reducer`に`ActionType`を渡している 55 | 56 | *ActionType* 57 | [import:3-4](../../examples/counter/js/actions/ActionCreator.js) 58 | 59 | *ActionCreator* 60 | [import:6-7](../../examples/counter/js/actions/ActionCreator.js) 61 | 62 | ### Reducer 63 | **役割** 64 | - `Action`から受け取った`ActionType`によって`state`を新規に返す 65 | 66 | [import:4-9](../../examples/counter/js/reducers/CounterReducer.js) 67 | 68 | `state`の値が変更されると`BaseComponent`の`constructor`で定義した`store.subscribe()`が実行される仕組みです。 69 | 70 | ## おわりに 71 | 今回のサンプルではデータフローをわかりやすくするために必要最低限の機能しかもっていませんでした。 72 | 他のサンプルでは`Action`にデータを渡して画面に反映させたり、非同期処理を行う場合はどうするかを説明していきますので、引き継ぎ頑張りましょう。 73 | -------------------------------------------------------------------------------- /examples/todo-answer/js/components/Todo.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import { TODO_STATUS, TAB } from '../contents/contents'; 3 | import * as actions from '../actions/ActionCreator'; 4 | import BaseComponent from '../../../utils/BaseComponent'; 5 | 6 | export default class Todo extends BaseComponent { 7 | constructor(selector, store) { 8 | super(selector, store, 'todo', 'tab'); 9 | 10 | this.$todoList = this.$selector.find('.js−todoList'); 11 | this.$todoTemplate = $('.js-template'); 12 | this.inputTodo = this.$selector.find('.js-todoInput'); 13 | this.$selector.find('.js-todoAddBtn').on('click', () => this.dispatch(actions.addTodo(this.inputTodo.val()))); 14 | this.$selector.find('.js-todoFilterAll').on('click', () => this.dispatch(actions.changeTab(TAB.ALL))); 15 | this.$selector.find('.js-todoFilterActive').on('click', () => this.dispatch(actions.changeTab(TAB.ACTIVE))); 16 | this.$selector.find('.js-todoFilterComplete').on('click', () => this.dispatch(actions.changeTab(TAB.COMPLETED))); 17 | 18 | this.$todoList.on('click', '.js-btnActive', (event) => this.dispatch(actions.activeTodo($(event.target).parent().attr('data-todo-id')))); 19 | this.$todoList.on('click', '.js-btnComplete', (event) => this.dispatch(actions.completeTodo($(event.target).parent().attr('data-todo-id')))); 20 | this.$todoList.on('click', '.js-btnDelete', (event) => this.dispatch(actions.deleteTodo($(event.target).parent().attr('data-todo-id')))); 21 | 22 | this.init(); 23 | } 24 | 25 | init() { 26 | this.dispatch(actions.initialTodo(JSON.parse($('.js-initialData').html()))); 27 | } 28 | 29 | render() { 30 | this.$todoList.html(this.state.todo 31 | .filter((todo) => this.state.tab === TAB.ALL || this.state.tab === todo.status) 32 | .map((todo) => this.renderTodo(todo)) 33 | ); 34 | } 35 | 36 | renderTodo(todo) { 37 | const $template = this.$todoTemplate.find(todo.status === TODO_STATUS.ACTIVE ? '.js-todoItemActive' : '.js-todoItemComplete').clone(); 38 | $template.attr('data-todo-id', todo.id); 39 | $template.find('.todoList-item-elem').text(todo.text); 40 | if (todo.status === TODO_STATUS.COMPLETED) { 41 | $template.addClass('completed'); 42 | $template.find('.complete').hide(); 43 | } 44 | if (todo.status === TODO_STATUS.ACTIVE) $template.find('.active').hide(); 45 | return $template; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/redux.md: -------------------------------------------------------------------------------- 1 | # Reduxの概要 2 | 3 | - [Redux公式サイト](http://rackt.org/redux/index.html) 4 | - [公式ドキュメント](http://redux.js.org/index.html) 5 | 6 | ## はじめに 7 | 初歩的な概念しか記載していませんが、以下の用語やデータフローは最低限理解しておきましょう。さらに理解を深めたい場合は公式ドキュメントを参照してください。 8 | 9 | ## Reduxの全体像 10 | ![](./img/redux-view.jpg) 11 | 12 | ## 3原則 13 | http://rackt.org/redux/docs/introduction/ThreePrinciples.html 14 | 15 | |1.Single source of truth(ソースは1つだけ)| 16 | |-| 17 | |アプリケーション全体の状態(`state`)はツリーの形で1つのオブジェクトで作られ、1つのストアに保存される。`state`が保存しやすいので、ユニバーサルアプリケーションがつくりやすい。`state`が1つだから、デバッグしやすい、開発しやすい。| 18 | 19 | |2.State is read-only(状態は読み取り専用)| 20 | |-| 21 | |状態を変更する手段は、変更内容をもったActionオブジェクトを発行して実行するだけ。ビューやコールバックが状態を直接変更させることはできない。変更は一つずつ順番に行なわれる。Actionはオブジェクトなので、保存可能であり、テストしやすい。| 22 | 23 | |3.Mutations are written as pure functions(変更はすべてpureな関数で書かれる)| 24 | |-| 25 | |アクションがどのように状態を変更するかを`Reducer`で行う。`Reducer`は状態とアクションを受けて新しい状態を返す関数です。現在の`state`を変更することはせずに、新しい`state`オブジェクトを作って返すというのがポイント。最初はアプリケーションで一つの`Reducer`を用意して、巨大化してきたら`Reducer`を分割していく。| 26 | 27 | ## 用語 28 | 29 | ### Action 30 | - アプリケーションからの情報を`store`へ送る為のオブジェクト 31 | - `store.dispatch()`で`store`へ送られる 32 | - 何を行なうものかを識別するために`type`プロパティを必ず持つ。この`type`を`ActionType`と呼ぶ。 33 | 34 | ```js 35 | const ADD_TODO = 'ADD_TODO'; 36 | { 37 | type: ADD_TODO, 38 | text: 'Build my first Redux app' 39 | }; 40 | ``` 41 | 42 | ### ActionCreator 43 | Actionを作る関数です。 44 | 45 | ```js 46 | function addTodo(text) { 47 | return { 48 | type: ADD_TODO, 49 | text: text 50 | }; 51 | } 52 | ``` 53 | 54 | `dispatch`するときは`creator`で作成した`action()`を渡します。 55 | 56 | ``` 57 | dispatch(addTodo(text)); 58 | ``` 59 | 60 | ### Store 61 | **役割** 62 | - `state`を保持する 63 | - `state`へアクセスするための`getState()`を提供する 64 | - `state`を更新するための`dispatch(action)`を提供する 65 | - リスナーを登録するための`subscribe(listener)`を提供する 66 | 67 | `store`は必ず1つのみ。`state`ごとにロジックを分割したい場合は、`store`を分割せずに`reducer composition`を使用してください。 68 | 69 | `store`をつくるには、`combineReducer`でつくられた`reducer`を`createStore()`へ渡します。 70 | 71 | ```js 72 | import { createStore } from 'redux'; 73 | import todoApp from './reducers'; 74 | const store = createStore(todoApp); 75 | ``` 76 | 77 | ### Reducer 78 | - 現在の`state`と`action`を受けて新しい`state`を返すだけの純粋なメソッドです。 79 | 80 | ```js 81 | (previousState, action) => newState; 82 | ``` 83 | 84 | `reducer`の中で以下のことをやってはいけません 85 | - 引数の`state`, `action`インスタンスの値を変更する 86 | - 副作用を起こす(APIを呼んだり、ルーティングを変えるなど) 87 | - 毎回値が変わるもの(`Date.now()`や`Math.random()`)を扱う 88 | 89 | ### Middleware 90 | - `dispatch`する前後にそれぞれ任意の処理を追加することができる仕組み 91 | - `dispatch`前後の`state`の状態をログ出力する等 92 | - npmに有志によって実装されたMiddlewareが上がっているので適宜使用する https://www.npmjs.com/search?q=redux%20middleware 93 | https://github.com/xgrommx/awesome-redux 94 | -------------------------------------------------------------------------------- /docs/redux_middleware.md: -------------------------------------------------------------------------------- 1 | # Redux Middleware 2 | 3 | ## redux-logger 4 | - https://github.com/fcomb/redux-logger 5 | - コンソールログに前の`state`と次の`state`を出力する 6 | 7 | ### Usage 8 | ```js 9 | import { createStore, applyMiddleware } from 'redux'; 10 | import createLogger from 'redux-logger'; 11 | import { RootReducer } from './reducers/Root'; 12 | 13 | const createStoreWithMiddleware = applyMiddleware(createLogger())(createStore); 14 | const store = createStoreWithMiddleware(RootReducer); 15 | ``` 16 | 17 | ## redux-actions 18 | - https://github.com/acdlite/redux-actions 19 | - `Action Creator`をラップし使いやすくしてくれる 20 | 21 | ### Usage 22 | 23 | *Action* 24 | ```js 25 | import { createAction } from 'redux-actions'; 26 | 27 | // ActionType 28 | export const INCREMENT = 'INCREMENT'; 29 | export const DECREMENT = 'DECREMENT'; 30 | 31 | // Action Creator 32 | export const increment = createAction(INCREMENT); 33 | export const decrement = createAction(DECREMENT); 34 | ``` 35 | 36 | *Reducer* 37 | ```js 38 | import { handleActions } from 'redux-actions'; 39 | import { INCREMENT, DECREMENT } from '../actions/ActionCreator'; 40 | 41 | const initialState = 0; 42 | 43 | export const CounterReducer = handleActions({ 44 | [INCREMENT]: (state) => state + 1, 45 | [DECREMENT]: (state) => state - 1 46 | }, initialState); 47 | ``` 48 | 49 | ## redux-thunk 50 | - https://github.com/gaearon/redux-thunk 51 | - `Action`で遅延評価して`dispatch`可能にする 52 | - `Action`で非同期処理を実装するときに利用する 53 | 54 | ### Usage 55 | ```js 56 | import { createStore, applyMiddleware } from 'redux'; 57 | import thunk from 'redux-thunk'; 58 | import rootReducer from './reducers/index'; 59 | 60 | // create a store that has redux-thunk middleware enabled 61 | const createStoreWithMiddleware = applyMiddleware(thunk)(createStore); 62 | 63 | const store = createStoreWithMiddleware(rootReducer); 64 | ``` 65 | 66 | *Action* 67 | ```js 68 | import fetch from 'isomorphic-fetch'; 69 | import { createAction } from 'redux-actions'; 70 | 71 | export const UPDATE_WEATHER = 'UPDATE_WEATHER'; 72 | export const FETCH_EXCEPTION = 'FETCH_EXCEPTION'; 73 | 74 | const WEATHER_API = 'http://api.openweathermap.org/data/2.5/weather?q=Tokyo,jp&APPID='; 75 | const API_KEY = '329465fbebeb9beb21a9d76142de6ce8'; 76 | 77 | const receiveWeather = createAction(UPDATE_WEATHER, (weather) => weather); 78 | const fetchException = createAction(FETCH_EXCEPTION, (message) => message); 79 | 80 | export function updateWeather() { 81 | return (dispatch) => { 82 | // fetch後にdispatchを実行 83 | return fetch(`${WEATHER_API}${API_KEY}`) 84 | .then(response => response.json()) 85 | .then(json => dispatch(receiveWeather(json))) 86 | .catch(() => dispatch(fetchException('failed get weather information.'))); 87 | }; 88 | } 89 | ``` -------------------------------------------------------------------------------- /examples/todo-answer/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -moz-box-sizing: border-box; 3 | -webkit-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | .l-body { 8 | border: 1px solid #DDD; 9 | color: #444; 10 | margin: 20px auto; 11 | padding: 20px; 12 | width: 95%; 13 | } 14 | 15 | .section { 16 | margin-bottom: 20px; 17 | } 18 | 19 | .title { 20 | border-bottom: 1px solid skyblue; 21 | border-left: 5px solid skyblue; 22 | font-size: 24px; 23 | font-weight: bold; 24 | margin-bottom: 20px; 25 | padding-left: 10px; 26 | } 27 | 28 | /* cf */ 29 | .cf:before, 30 | .cf:after { 31 | content: " "; 32 | display: table; 33 | } 34 | 35 | .cf:after { 36 | clear: both; 37 | } 38 | 39 | .cf { 40 | *zoom: 1; 41 | } 42 | 43 | /* input */ 44 | .mod-input { 45 | appearance: none; 46 | border: 1px solid cadetblue; 47 | margin-right: 10px; 48 | 49 | -webkit-appearance: none; 50 | -moz-appearance: none; 51 | } 52 | 53 | /* btn */ 54 | .mod-btn { 55 | border: 1px solid cadetblue; 56 | color: cadetblue; 57 | cursor: pointer; 58 | display: inline-block; 59 | min-width: 90px; 60 | padding: 1px 7px; 61 | text-align: center; 62 | } 63 | 64 | .mod-btn:hover, 65 | .mod-btn:focus { 66 | background-color: cadetblue; 67 | border: 1px solid #EEE; 68 | color: white; 69 | opacity: .75; 70 | -webkit-transition: all .3s; 71 | -moz-transition: all .3s; 72 | -ms-transition: all .3s; 73 | -o-transition: all .3s; 74 | transition: all .3s; 75 | } 76 | 77 | .mod-btn.is-active { 78 | background-color: cadetblue; 79 | border: 1px solid #EEE; 80 | color: white; 81 | } 82 | 83 | .mod-btn_delete { 84 | border-color: darkgray; 85 | color: darkgray; 86 | } 87 | 88 | .mod-btn_delete:hover, 89 | .mod-btn_delete:focus, 90 | .mod-btn_delete.is-active { 91 | background-color: darkgray; 92 | } 93 | 94 | .mod-btn_complete { 95 | border-color: orangered; 96 | color: orangered; 97 | } 98 | 99 | .mod-btn_complete:hover, 100 | .mod-btn_complete:focus, 101 | .mod-btn_complete.is-active { 102 | background-color: orangered; 103 | } 104 | 105 | .mod-btn_active { 106 | border-color: yellowgreen; 107 | color: yellowgreen; 108 | } 109 | 110 | .mod-btn_active:hover, 111 | .mod-btn_active:focus, 112 | .mod-btn_active.is-active { 113 | background-color: yellowgreen; 114 | } 115 | 116 | /* list */ 117 | .todoList { 118 | border-bottom: 1px solid dodgerblue; 119 | border-top: 1px solid dodgerblue; 120 | } 121 | 122 | .todoList-item { 123 | border-bottom: 1px solid dodgerblue; 124 | padding: 10px; 125 | } 126 | 127 | .todoList-item-text { 128 | float: left; 129 | } 130 | 131 | .todoList-item-btn { 132 | float: right; 133 | margin-right: 10px; 134 | } 135 | 136 | .todoList-item.is-complete { 137 | background-color: #fafafa; 138 | color: #AAA; 139 | } 140 | 141 | .todoList-item.is-complete .todoList-item-elem { 142 | text-decoration: line-through; 143 | } 144 | 145 | .todoList-item:last-child { 146 | border-bottom: none; 147 | } 148 | -------------------------------------------------------------------------------- /examples/todo-answer/js/test/reducers/TodoReducer.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import {TodoReducer} from '../../reducers/TodoReducer'; 4 | import {TODO_STATUS} from '../../contents/contents'; 5 | 6 | test('should provide the initial state', (t) => { 7 | t.deepEqual(TodoReducer(undefined, {}), [{id: 0, text: 'todo', status: TODO_STATUS.ACTIVE}]); 8 | }); 9 | 10 | test('should handle INITIAL_TODO action', (t) => { 11 | t.deepEqual(TodoReducer( 12 | [{id: 0, text: 'todo', status: TODO_STATUS.ACTIVE}], 13 | {type: 'INITIAL_TODO', payload: [ 14 | {id: 1, text: 'Counter', status: TODO_STATUS.COMPLETED}, 15 | {id: 2, text: 'Greeting', status: TODO_STATUS.COMPLETED}, 16 | {id: 3, text: 'Weather', status: TODO_STATUS.ACTIVE}, 17 | {id: 4, text: 'TODO', status: TODO_STATUS.ACTIVE}]} 18 | ), 19 | [ 20 | {id: 1, text: 'Counter', status: TODO_STATUS.COMPLETED}, 21 | {id: 2, text: 'Greeting', status: TODO_STATUS.COMPLETED}, 22 | {id: 3, text: 'Weather', status: TODO_STATUS.ACTIVE}, 23 | {id: 4, text: 'TODO', status: TODO_STATUS.ACTIVE} 24 | ] 25 | ); 26 | }); 27 | 28 | test('should handle ADD_TODO action', (t) => { 29 | t.deepEqual(TodoReducer( 30 | [], 31 | {type: 'ADD_TODO', payload: {text: 'TODO'}} 32 | ), 33 | [ 34 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE} 35 | ] 36 | ); 37 | 38 | t.deepEqual(TodoReducer( 39 | [ 40 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE} 41 | ], 42 | {type: 'ADD_TODO', payload: {text: 'write tests'}} 43 | ), 44 | [ 45 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE}, 46 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 47 | ] 48 | ); 49 | 50 | t.deepEqual(TodoReducer( 51 | [ 52 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE}, 53 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 54 | ], 55 | {type: 'ADD_TODO', payload: {text: 'fix tests'}} 56 | ), 57 | [ 58 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE}, 59 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE}, 60 | {id: 2, text: 'fix tests', status: TODO_STATUS.ACTIVE} 61 | ] 62 | ); 63 | }); 64 | 65 | 66 | test('should handle DELETE_TODO action', (t) => { 67 | t.deepEqual(TodoReducer( 68 | [ 69 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE}, 70 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE}, 71 | {id: 2, text: 'fix tests', status: TODO_STATUS.ACTIVE} 72 | ], 73 | {type: 'DELETE_TODO', payload: 2} 74 | ), 75 | [ 76 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE}, 77 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 78 | ] 79 | ); 80 | 81 | t.deepEqual(TodoReducer( 82 | [ 83 | {id: 0, text: 'TODO', status: TODO_STATUS.ACTIVE}, 84 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 85 | ], 86 | {type: 'DELETE_TODO', payload: 0} 87 | ), 88 | [ 89 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 90 | ] 91 | ); 92 | 93 | t.deepEqual(TodoReducer( 94 | [ 95 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 96 | ], 97 | {type: 'DELETE_TODO', payload: 1} 98 | ), 99 | [] 100 | ); 101 | }); 102 | 103 | test('should handle ACTIVE_TODO action', (t) => { 104 | t.deepEqual(TodoReducer( 105 | [ 106 | {id: 1, text: 'write tests', status: TODO_STATUS.COMPLETED} 107 | ], 108 | {type: 'ACTIVE_TODO', payload: 1} 109 | ), 110 | [ 111 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 112 | ] 113 | ); 114 | }); 115 | 116 | test('should handle COMPLETE_TODO action', (t) => { 117 | t.deepEqual(TodoReducer( 118 | [ 119 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 120 | ], 121 | {type: 'COMPLETE_TODO', payload: 1} 122 | ), 123 | [ 124 | {id: 1, text: 'write tests', status: TODO_STATUS.COMPLETED} 125 | ] 126 | ); 127 | }); 128 | 129 | test('should handle unknown actions', (t) => { 130 | t.deepEqual(TodoReducer( 131 | [ 132 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 133 | ], 134 | {type: 'undefined'} 135 | ), 136 | [ 137 | {id: 1, text: 'write tests', status: TODO_STATUS.ACTIVE} 138 | ] 139 | ); 140 | }); 141 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "$": true, 9 | "env": true 10 | }, 11 | "parser": "babel-eslint", 12 | "plugins": ["babel"], 13 | "rules": { 14 | "no-alert": 0, 15 | "no-array-constructor": 2, 16 | "no-bitwise": 0, 17 | "no-caller": 2, 18 | "no-catch-shadow": 2, 19 | "no-comma-dangle": 0, 20 | "no-cond-assign": 2, 21 | "no-console": 0, 22 | "no-constant-condition": 2, 23 | "no-continue": 0, 24 | "no-control-regex": 2, 25 | "no-debugger": 2, 26 | "no-delete-var": 2, 27 | "no-div-regex": 0, 28 | "no-dupe-keys": 2, 29 | "no-dupe-args": 2, 30 | "no-duplicate-case": 2, 31 | "no-else-return": 0, 32 | "no-empty": 2, 33 | "no-empty-class": 0, 34 | "no-empty-character-class": 2, 35 | "no-eq-null": 0, 36 | "no-eval": 2, 37 | "no-ex-assign": 2, 38 | "no-extend-native": 2, 39 | "no-extra-bind": 2, 40 | "no-extra-boolean-cast": 2, 41 | "no-extra-parens": 0, 42 | "no-extra-semi": 2, 43 | "no-fallthrough": 2, 44 | "no-floating-decimal": 0, 45 | "no-func-assign": 2, 46 | "no-implied-eval": 2, 47 | "no-inline-comments": 0, 48 | "no-inner-declarations": [2, "functions"], 49 | "no-invalid-regexp": 2, 50 | "no-irregular-whitespace": 2, 51 | "no-iterator": 2, 52 | "no-label-var": 2, 53 | "no-labels": 2, 54 | "no-lone-blocks": 2, 55 | "no-lonely-if": 0, 56 | "no-loop-func": 2, 57 | "no-mixed-requires": [0, false], 58 | "no-mixed-spaces-and-tabs": [2, false], 59 | "linebreak-style": [0, "unix"], 60 | "no-multi-spaces": 2, 61 | "no-multi-str": 2, 62 | "no-multiple-empty-lines": [0, {"max": 2}], 63 | "no-native-reassign": 2, 64 | "no-negated-in-lhs": 2, 65 | "no-nested-ternary": 0, 66 | "no-new": 0, 67 | "no-new-func": 2, 68 | "no-new-object": 2, 69 | "no-new-require": 0, 70 | "no-new-wrappers": 2, 71 | "no-obj-calls": 2, 72 | "no-octal": 2, 73 | "no-octal-escape": 2, 74 | "no-param-reassign": 0, 75 | "no-path-concat": 0, 76 | "no-plusplus": 0, 77 | "no-process-env": 0, 78 | "no-process-exit": 2, 79 | "no-proto": 2, 80 | "no-redeclare": 2, 81 | "no-regex-spaces": 2, 82 | "no-reserved-keys": 0, 83 | "no-restricted-modules": 0, 84 | "no-return-assign": 2, 85 | "no-script-url": 2, 86 | "no-self-compare": 0, 87 | "no-sequences": 2, 88 | "no-shadow": 0, 89 | "no-shadow-restricted-names": 2, 90 | "no-space-before-semi": 0, 91 | "no-spaced-func": 2, 92 | "no-sparse-arrays": 2, 93 | "no-sync": 0, 94 | "no-ternary": 0, 95 | "no-trailing-spaces": 2, 96 | "no-this-before-super": 0, 97 | "no-throw-literal": 0, 98 | "no-undef": 2, 99 | "no-undef-init": 2, 100 | "no-undefined": 0, 101 | "no-unexpected-multiline": 0, 102 | "no-underscore-dangle": 2, 103 | "no-unneeded-ternary": 0, 104 | "no-unreachable": 2, 105 | "no-unused-expressions": [2, {"allowShortCircuit": true, "allowTernary": true}], 106 | "no-unused-vars": [2, {"vars": "local", "args": "after-used"}], 107 | "no-use-before-define": 2, 108 | "no-void": 0, 109 | "no-var": 0, 110 | "prefer-const": 0, 111 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 112 | "no-with": 2, 113 | "array-bracket-spacing": [0, "never"], 114 | "accessor-pairs": 0, 115 | "block-scoped-var": 0, 116 | "brace-style": [0, "1tbs"], 117 | "camelcase": 2, 118 | "comma-dangle": [2, "never"], 119 | "comma-spacing": 2, 120 | "comma-style": 0, 121 | "complexity": [0, 11], 122 | "computed-property-spacing": [0, "never"], 123 | "consistent-return": 2, 124 | "consistent-this": [0, "that"], 125 | "constructor-super": 0, 126 | "curly": [2, "multi-line"], 127 | "default-case": 0, 128 | "dot-location": 0, 129 | "dot-notation": [2, { "allowKeywords": true }], 130 | "eol-last": 2, 131 | "eqeqeq": 2, 132 | "func-names": 0, 133 | "func-style": [0, "declaration"], 134 | "generator-star": 0, 135 | "generator-star-spacing": 0, 136 | "guard-for-in": 0, 137 | "handle-callback-err": 0, 138 | "indent": 0, 139 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 140 | "lines-around-comment": 0, 141 | "max-depth": [0, 4], 142 | "max-len": [0, 80, 4], 143 | "max-nested-callbacks": [0, 2], 144 | "max-params": [0, 3], 145 | "max-statements": [0, 10], 146 | "new-cap": 2, 147 | "new-parens": 2, 148 | "newline-after-var": 0, 149 | "object-curly-spacing": [0, "never"], 150 | "object-shorthand": 0, 151 | "one-var": 0, 152 | "operator-assignment": [0, "always"], 153 | "operator-linebreak": 0, 154 | "padded-blocks": 0, 155 | "quote-props": 0, 156 | "quotes": [2, "single"], 157 | "radix": 0, 158 | "semi": 2, 159 | "semi-spacing": [2, {"before": false, "after": true}], 160 | "sort-vars": 0, 161 | "space-after-function-name": [0, "never"], 162 | "space-after-keywords": [0, "always"], 163 | "space-before-blocks": [0, "always"], 164 | "space-before-function-paren": [0, "always"], 165 | "space-before-function-parentheses": [0, "always"], 166 | "space-in-brackets": [0, "never"], 167 | "space-in-parens": [0, "never"], 168 | "space-infix-ops": 2, 169 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 170 | "spaced-comment": 0, 171 | "spaced-line-comment": [0, "always"], 172 | "strict": 0, 173 | "use-isnan": 2, 174 | "valid-jsdoc": 0, 175 | "valid-typeof": 2, 176 | "vars-on-top": 0, 177 | "wrap-iife": 0, 178 | "wrap-regex": 0, 179 | "yoda": [2, "never"], 180 | "babel/generator-star-spacing": 0, 181 | "babel/new-cap": 0, 182 | "babel/object-curly-spacing": 0, 183 | "babel/object-shorthand": 0, 184 | "babel/arrow-parens": 0, 185 | "babel/no-await-in-loop": 0 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /docs/es6.md: -------------------------------------------------------------------------------- 1 | # ES2015 Frist Step 2 | 3 | ## はじめに 4 | 5 | `JavaScript`は`ECMAScript`という仕様のもとに実装されています。`ECMAScript2015(ES2015)`は`ECMAScript`の新しい標準仕様です。この仕様は2015年6月に確定しました。 6 | `ES5`から6年ぶりのメジャーアップデートのため最初は違和感があるかもしれません。 7 | しかし、より便利になるアップデートですのでしっかりキャッチアップし積極的に使用していきましょう。 8 | 9 | ### ES6 or ES2015? 10 | 11 | ネット上で`ES6`と`ES2015`を見かけると思いますが、両方とも同じものを指しています。正確には`ES2015`が正しいです。来年以降も`ES2016`,`ES2017`…とアップデート予定です。当初は`ES6`として仕様検討が始まったためその名残りです。 12 | 13 | ### Babel 14 | 15 | **背景** 16 | 17 | `ES2015`の仕様確定が2015年6月にされましたが、すぐに新仕様が使えるわけではありません。JavaScriptエンジン\(V8,JavaScriptCore,Chakra等\)の実装は現在進行中でまだ使えない機能があるためです。[こちら](https://kangax.github.io/compat-table/es6/)のサイトで各ブラウザに実装済みの機能が確認できます。 18 | 19 | ブラウザとJavaScriptエンジンの対応は以下の表のとおりです。 20 | 21 | | ブラウザ | JavaScriptエンジン | 22 | | :--- | :--- | 23 | | IE | Chakra | 24 | | Chrome | V8 | 25 | | Opera | V8 | 26 | | Safari | JavaScriptCore | 27 | | Firefox | SpiderMonkey | 28 | 29 | **Babel** 30 | 31 | ブラウザに新仕様が追加されるまで新仕様を導入することがきません。そこで登場したのが`Babel(6to5)`で、`Babel`は`ES2015,ES Next`のコードを`ES5`のコードにトランスパイルしてくれるツールです\([公式サイト](https://babeljs.io/)\)。`ES2016`,`ES2017`の様な次期仕様を`ES Next`と呼んでいます。ブラウザはトランスパイル後のJavaScriptコードを読み込むことになります。 32 | 33 | ## ES2015新規仕様 34 | 35 | ここでは特によく使われている仕様を説明します。 36 | 37 | ### let, const 38 | 39 | `let`, `const`はブロックスコープの変数宣言かできます。`const`は再代入不可な値を宣言します。これまで使われていた`var`は使用しないようにしましょう。基本は`const`、再代入が必要な場合のみ`let`を使用するようにしましょう。 40 | 41 | ```js 42 | // ES5 43 | var a = 1; 44 | 45 | // ES2015 46 | let b = 1; // 再代入不可能な値(再代入が必要なときのみ使用する) 47 | const c = 1; // 再代入不可な値(推奨) 48 | ``` 49 | 50 | `let`, `const`は`var`とはスコープのルールが違います。 51 | 52 | ```js 53 | function varTest() { 54 | var x = 1; 55 | if (true) { 56 | var x = 2; // same variable! 57 | console.log(x); // 2 58 | } 59 | console.log(x); // 2 60 | } 61 | 62 | function letTest() { 63 | let x = 1; 64 | if (true) { 65 | let x = 2; // different variable 66 | console.log(x); // 2 67 | } 68 | console.log(x); // 1 69 | } 70 | ``` 71 | 72 | これは`var`がグローバルオブジェクトの`property`となるためです。\(ブラウザの場合は`window object`\) 73 | 74 | ```js 75 | var x = 'global'; 76 | let y = 'global'; 77 | console.log(this.x); // "global" 78 | console.log(this.y); // undefined 79 | ``` 80 | 81 | また、`var`との違いとして、`Temporal Dead Zone(TDZ)`があります。変数宣言は、ブロックの先頭か、変数を使用する直前で宣言するようにしましょう。 82 | 83 | ```js 84 | { 85 | console.log(a); // undefined 86 | console.log(b); // Reference Error! 87 | 88 | var a; 89 | let b; 90 | } 91 | ``` 92 | 93 | `const`は変更不可と説明しましたが、配列やオブジェクトの要素は変更が可能です。 94 | 95 | ```js 96 | { 97 | const a = [1,2,3]; 98 | a.push(4); 99 | console.log(a); // 1,2,3,4 100 | 101 | a = 42; // Type Error! 102 | } 103 | ``` 104 | 105 | 配列やオブジェクトの要素も変更不可にするには[Object.freeze()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)や[immutable.js](https://facebook.github.io/immutable-js/)等を使うことになります。 106 | 107 | ### Templete Strings 108 | 109 | バッククォートを使った文字列の宣言ができます。メリットは文字列結合が簡単に書けることです。バッククォートで囲った文字列の`${}`は展開されます。またバッククォート内は改行できます。 110 | 111 | ```js 112 | // ES5 113 | var errorCode = 404; 114 | var errorMessage = `file not found.`; 115 | console.error('Error!! Code: ' + errorCode + ', Message: ' + errorMessage); 116 | // Error!! Code: 404, Message: file not found. 117 | 118 | // ES2015 119 | const errorCode = 404; 120 | const errorMessage = `file not found.`; 121 | console.error(`Error!! Code: ${errorCode}, Message: ${errorMessage}`); 122 | // Error!! Code: 404, Message: file not found. 123 | ``` 124 | 125 | ### Class 126 | 127 | クラスの宣言ができます。`extends`による継承や、`instance object`が生成直後に実行される`constructor`が使用できます。 128 | 129 | ```js 130 | class User { 131 | constructor(name) { 132 | this.name = name; 133 | } 134 | 135 | say() { 136 | return `My name is ${this.name}`; 137 | } 138 | } 139 | 140 | // Userクラスを継承してAdminクラスを作る 141 | class Admin extends User { 142 | say() { 143 | return `[Administrator] ${super.say()}`; 144 | } 145 | } 146 | 147 | const user = new User('Alice'); 148 | console.log(user.say()); // My name is Alice 149 | 150 | const admin = new Admin('Bob'); 151 | console.log(admin.say()); // [Administrator] My name is Bob 152 | ``` 153 | 154 | ### Default Parameters 155 | 156 | 関数の引数にデフォルト値を設定できます。 157 | 158 | ```js 159 | // ES5 160 | function consoleName(name) { 161 | name = (name != undefined) ? name : 'Taro'; 162 | console.log('username: ' + name); 163 | } 164 | 165 | // ES2015 166 | function consoleName(name = 'Taro') { 167 | console.log(`username: ${name}`); 168 | } 169 | 170 | consoleName(); // Taro 171 | consoleName('Masaya'); // Masaya 172 | ``` 173 | 174 | ### Arrow Function 175 | 176 | functionを`=>`に置換できます。 177 | 178 | ```js 179 | // ES5 180 | $('.hoge').on('change', function(event) { 181 | console.log(event); 182 | }); 183 | 184 | // ES2015 185 | $('.hoge').on('change', (event) => { 186 | console.log(event); 187 | }); 188 | // 中身が式なら`{}`を省略可能 189 | $('.hoge').on('change', (event) => console.log(event)); 190 | 191 | // 式で書いた場合は、結果がreturnされます 192 | const multify = (val) => val * val; 193 | console.log(multify(5)); // 25 194 | ``` 195 | 196 | **※注意点** 197 | 198 | `this`の扱いが`function`と`=>`で異なります。 199 | 200 | ```js 201 | class StatusCode { 202 | constructor(statusCode) { 203 | this.statusCode = statusCode; 204 | 205 | setTimeout(() => { 206 | console.log(this.statusCode, 'OK'); 207 | }, 1000); 208 | 209 | setTimeout(() => console.log(this.statusCode, 'OK'), 1000); 210 | 211 | const self = this; 212 | setTimeout(function() { 213 | if (this.statusCode === undefined) console.log('`this.statusCode` is `undefined` in function'); 214 | console.log(self.statusCode, 'OK'); 215 | }, 1000); 216 | } 217 | } 218 | 219 | const statusCode = new StatusCode(200); 220 | ``` 221 | 222 | `Allow Function`内の`this`は`StatusCode`クラスの`this`を参照するのに対し、`function()`はグローバオブジェクトの`this`を参照している。グローバルオブジェクトの`this`に`statusCode`は宣言していないので`undefined`となります。 223 | 224 | ### Enhanced Object Literals 225 | 226 | オブジェクトリテラルが拡張されました。 227 | 228 | ```js 229 | // ES5 230 | var obj = { "name": "Masaya Kamakura" }; 231 | 232 | // ES2015 233 | // key名とvalueの変数名が同じであれば省略可能 234 | function getNameObject(name) { 235 | return { name }; 236 | } 237 | console.log(getNameObject('Masaya Kamakura')); // {"name":"Masaya Kamakura"} 238 | 239 | // key名を動的に宣言可能 240 | function getNameObject(name) { 241 | const nameKey = 'fullName'; 242 | return { [nameKey]: name }; // keyに変数を使用 243 | } 244 | console.log(getNameObject('Masaya Kamakura')); // {"fullName":"Masaya Kamakura"} 245 | 246 | function getNameObject(name) { 247 | return { [(() => 'fullName')()]: name }; // keyに関数を使用 248 | } 249 | console.log(getNameObject('Masaya Kamakura')); // {"fullName":"Masaya Kamakura"} 250 | ``` 251 | 252 | ### String Object APIs 253 | 254 | APIがいくつか追加されました。ここでは`includes`と`repeat`、`startsWith`を紹介します。その他の機能は[こちら](https://kangax.github.io/compat-table/es6/#test-String.prototype_methods)を参考にしてください。 255 | 256 | ```js 257 | "abcde".includes("cd") // true 258 | "abcde".includes("dd") // false 259 | 260 | "abc".repeat(3) // "abcabcabc" 261 | 262 | "abcde".startsWith("abc") // true 263 | "abcde".startsWith("de") // false 264 | ``` 265 | 266 | * includes\(\) 267 | * 引数に指定した文字列が含まれていれば`true`、以外は`false`を返す 268 | 269 | * repeat\(\) 270 | * 引数に指定した回数だけ繰り返した文字列を返す 271 | 272 | * startsWith\(\) 273 | * 引数に指定した文字列で開始していれば`true`、以外は`false`を返す 274 | 275 | ### Spread 276 | 277 | 配列を展開して返します。 278 | 配列に要素を追加して、新しい配列を作るときによく利用されます。 279 | 280 | ```js 281 | const list = [1, 2, 3]; 282 | console.log(...list); // 1 2 3 283 | 284 | // 先頭に要素を追加する 285 | const newList = [0, ...list]; 286 | console.log(newList); // [0, 1, 2, 3] 287 | 288 | // Default Parametersと組み合わせて使う 289 | function foo(x, y, ...z) { 290 | console.log(x, y, z); 291 | } 292 | foo(1, 2, 3, 4, 5); // 1 2 [3,4,5] 293 | ``` 294 | 295 | ### Import\/Export 296 | 297 | * `import`: 外部モジュールを読み込む 298 | * `export`: 外部モジュールで利用できるようにする 299 | 300 | ```js 301 | // a.js 302 | import * as fuga from "b"; // exportされたすべてのモジュールを読み込む 303 | import { hoge1, hoge2 } from "b"; // hoge1, hoge2を読み込む 304 | import foo from "b"; // `{}`をつけないとexport defaultされたモジュールを読み込む 305 | ``` 306 | 307 | ```js 308 | // b.js 309 | export default function foo() {}; 310 | export function hoge1() {}; 311 | export function hoge2() {}; 312 | ``` 313 | 314 | ### Object.assign\(target, ...sources\) 315 | 316 | 1つ以上の`source`オブジェクトの保有する全てのプロパティを`target`にコピーします。戻り値は`target`オブジェクトになります。 317 | 318 | ```js 319 | const todo = { 320 | id: 1, 321 | text: 'catch up es6', 322 | status: 'pending' 323 | }; 324 | 325 | const newTodo = Object.assign({}, todo, {status: 'progress'}); 326 | console.log(newTodo); 327 | // { "id": 1, "text": "catch up es6", "status": "progress" } 328 | ``` 329 | 330 | ### Promise 331 | 332 | `callback`のような非同期プログラミングで使用する。 333 | 334 | `callback`を使った例 335 | 336 | ```js 337 | getAsync("fileA.txt", (error, result) => { 338 | if(error){// 取得失敗時の処理 339 | throw error; 340 | } 341 | // 取得成功の処理 342 | }); 343 | ``` 344 | 345 | `Promise`を使った例 346 | 347 | ```js 348 | var promise = getAsyncPromise("fileA.txt"); // textを取得してPromise Objectを返す 349 | promise.then((result) => { 350 | // 取得成功の処理 351 | }).catch((error) => { 352 | // 取得失敗時の処理 353 | }); 354 | ``` 355 | 356 | `getAsyncPromise()`は`Promise Object`を返す`function`です。処理が成功した場合`then()`。失敗した場合`catch()`が実行されます。 357 | 358 | 次に、`getAsyncPromise()`の中身を見てみましょう。 359 | 360 | ```js 361 | function asyncFunction() { 362 | return new Promise(function (resolve, reject) { 363 | setTimeout(function () { 364 | if (false) { // この例では実行させない 365 | reject(new Error('ERROR!')); 366 | } 367 | resolve('Async Hello world'); 368 | }, 16); 369 | }); 370 | } 371 | ``` 372 | 373 | `Promise`には`Promise.all()`,`Promise.race()`という機能が用意されています。どちらも並列に非同期処理を実行させることができます。違いは`then`,`catch`が実行されるタイミングです。 374 | 375 | ``` 376 | // Promise.all()のthen()はすべての処理が完了(resolve/reject)したら実行される 377 | Promise.all([getAsyncPromise(url1), getAsyncPromise(url2), getAsyncPromise(url3)]) 378 | .then(results => console.log(results)) 379 | .catch(error => console.log(error)); 380 | 381 | // Promise.race()のthen()はどれか一つでも完了(resolve/reject)したら実行される(他の処理が中断されることではない) 382 | Promise.race([getAsyncPromise(url1), getAsyncPromise(url2), getAsyncPromise(url3)]) 383 | .then(results => console.log(results)) 384 | .cache(error => console.log(error)); 385 | ``` 386 | 387 | このような機能のおかげで`callback地獄`になりにくいコード書くことができます。上記の`Promise.all()`の例を`callback`で書くこと以下のようになります。\(error処理は省略\) 388 | 389 | ``` 390 | asyncFunction().then(function (value) { 391 | console.log(value); // 'Async Hello world' 392 | }).catch(function (error) { 393 | console.log(error); // reject()が実行された場合はこの行が処理される 394 | }); 395 | ``` 396 | 397 | _※補足_ 398 | `Promise`_以外にも_`generator(ES2015)`_や_`aync function(ES Next)`_のような非同期プログラミングをするための機能があります。_ 399 | 400 | ## おまけ:ES5で積極的に使ってほしい機能 401 | 402 | ### Array.prototype.forEach\(\) 403 | 404 | 与えられた関数を、配列の各要素に対して一度ずつ実行します。 405 | 406 | ```js 407 | const data = [1, 2, 3, 4, 5]; 408 | data.forEach((val) => console.log(val)); 409 | ``` 410 | 411 | for文だとループ変数が必要であったり、階層が深くなると可読性が下がるので使用しないようにしましょう。 412 | 413 | ### Array.prototype.map\(\) 414 | 415 | 与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。 416 | `Array.prototype.forEach()`と似ていますが、新しい配列を生成するところに違いがあります。 417 | 418 | ```js 419 | const data = [1, 2, 3, 4, 5]; 420 | const square = data.map((val) => val * val); 421 | console.log(square); // [1, 4, 9, 16, 25] 422 | ``` 423 | 424 | ### Array.prototype.filter\(\) 425 | 426 | 引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。 427 | 428 | ```js 429 | const data = [1, 2, 3, 4, 5]; 430 | // 要素が4未満の場合のみ2乗した結果を返す 431 | const filterSquare = data.filter((val) => val < 4) 432 | .map((val) => val * val); 433 | console.log(filterSquare); // [1, 4, 9] 434 | ``` 435 | 436 | ### Array.prototype.reduce\(\) 437 | 438 | 隣り合う 2 つの配列要素に対して(左から右へ)同時に関数を適用し、単一の値にします。 439 | 440 | ```js 441 | const data = [1, 2, 3, 4, 5]; 442 | 443 | const sum = data.reduce((pre, current) => pre + current); 444 | console.log(sum); // 15 445 | 446 | const max = data.reduce((pre, current) => Math.max(pre, current)); 447 | console.log(max); // 5 448 | ``` 449 | 450 | **sumの例** 451 | 452 | | | pre | current | return | 453 | | :--- | :--- | :--- | :--- | 454 | | 1st | 1 | 2 | 3 | 455 | | 2nd | 3 | 3 | 6 | 456 | | 3rd | 6 | 4 | 10 | 457 | | 4th | 10 | 5 | 15 | 458 | 459 | 結果: 15 460 | 461 | **maxの例** 462 | 463 | | | pre | current | return | 464 | | :--- | :--- | :--- | :--- | 465 | | 1st | 1 | 2 | 2 | 466 | | 2nd | 2 | 3 | 3 | 467 | | 3rd | 3 | 4 | 4 | 468 | | 4th | 4 | 5 | 5 | 469 | 470 | 結果: 5 471 | 472 | ## おわりに 473 | 474 | この資料で紹介した機能はES2015の仕様でもごく一部です。さらに勉強したい方のために参考資料を残しておきます。 475 | またES5で便利な関数も紹介しました。`for`,`if`,`switch`等を使わなくても書けることが多いので、ES5の機能で書けないか考えるようにしましょう。 476 | 477 | ## 参考資料 478 | 479 | * [https:\/\/babeljs.io\/](https://babeljs.io/) 480 | * [https:\/\/babeljs.io\/docs\/learn-es2015\/](https://babeljs.io/docs/learn-es2015/) 481 | * [http:\/\/sssslide.com\/www.slideshare.net\/teppeis\/es6-in-practice](http://sssslide.com/www.slideshare.net/teppeis/es6-in-practice) 482 | * [https:\/\/kangax.github.io\/compat-table\/es6\/](https://kangax.github.io/compat-table/es6/) 483 | * [https:\/\/developer.mozilla.org\/ja\/docs\/Web\/JavaScript](https://developer.mozilla.org/ja/docs/Web/JavaScript) 484 | * [http:\/\/azu.github.io\/promises-book\/](http://azu.github.io/promises-book/) 485 | 486 | --------------------------------------------------------------------------------