Redux
Redux is a predictable state container for JavaScript apps.
https://github.com/reduxjs/redux
https://cdnjs.cloudflare.com/ajax/libs/redux/3.3.1/redux.js
概念
view 點擊 => action => reducer => store => 回傳 state 給 view
1.state 統一由 store 保存,任何更新 state 都要告知 store
2.讓 views 得到 store 中 state的方法
1.使用connect讓最上層元件取得Provider中的store,再用props傳下去
2.使用store.getState
簡單範例
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="https://npmcdn.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div>
<p>
Clicked: <span id="value">0</span> times
<button id="increment">+</button>
<button id="decrement">-</button>
<button id="incrementIfOdd">Increment if odd</button>
<button id="incrementAsync">Increment async</button>
</p>
</div>
</body>
</html>
<script>
var store = Redux.createStore(counter)
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().toString()
}
render()
store.subscribe(render)
</script>
Reducer
function counter(state, action) {
if (typeof state === 'undefined') {
return 0
}
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
document.getElementById('increment')
.addEventListener('click', function () {
store.dispatch({ type: 'INCREMENT' })
})
document.getElementById('decrement')
.addEventListener('click', function () {
store.dispatch({ type: 'DECREMENT' })
})
document.getElementById('incrementIfOdd')
.addEventListener('click', function () {
if (store.getState() % 2 !== 0) {
store.dispatch({ type: 'INCREMENT' })
}
})
document.getElementById('incrementAsync')
.addEventListener('click', function () {
setTimeout(function () {
store.dispatch({ type: 'INCREMENT' })
}, 1000)
})
使用React連結Redux
https://github.com/reactjs/redux/tree/master/examples/counter
建立counter的元件(原本的HTML)
import React, { Component, PropTypes } from 'react'
class Counter extends Component {
constructor(props) {
super(props)
this.incrementAsync = this.incrementAsync.bind(this)
this.incrementIfOdd = this.incrementIfOdd.bind(this)
}
incrementIfOdd() {
if (this.props.value % 2 !== 0) {
this.props.onIncrement()
}
}
incrementAsync() {
setTimeout(this.props.onIncrement, 1000)
}
render() {
const { value, onIncrement, onDecrement } = this.props
return (
<p>
Clicked: {value} times
{' '}
<button onClick={onIncrement}>
+
</button>
{' '}
<button onClick={onDecrement}>
-
</button>
{' '}
<button onClick={this.incrementIfOdd}>
Increment if odd
</button>
{' '}
<button onClick={this.incrementAsync}>
Increment async
</button>
</p>
)
}
}
Counter.propTypes = {
value: PropTypes.number.isRequired,
onIncrement: PropTypes.func.isRequired,
onDecrement: PropTypes.func.isRequired
}
export default Counter
render到html
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import counter from './reducers'
const store = createStore(counter)
const rootEl = document.getElementById('root')
function render() {
ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
/>,
rootEl
)
}
render()
store.subscribe(render)
最後定義reducer
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
完成
上例我們沒有直接去定義action.js而是直接指定action的type
我們可改寫成
onIncrement={() => store.dispatch(increment(text))}
onDecrement={() => store.dispatch(decrement(text))}
action.js
function increment(text) {
return {
type: INCREMENT,
//text
};
}
function decrement(text) {
return {
type: DECREMENT,
//text
};
}
另一個稍為更詳細的範例
流程:
1.定義一個action物件
2.定義reducer(對應不同action型態做不同處理)
3.綁定reducer給createStore
4.view發出dispatch(action) 傳給reducer再更新store
5.Store傳回給view
實做:
1.
package.json
{
"name": "react-todo-list",
"version": "1.0.0",
"description": "A simple todo list app built with React, Redux and Webpack",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "nodemon server/server.js --ignore components"
},
"repository": {
"type": "git",
"url": "https://github.com/kweiberth/react-todo-list.git"
},
"author": "Kurt Weiberth",
"license": "ISC",
"dependencies": {
"babel-core": "^6.4.5",
"babel-loader": "^6.2.2",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-react-hmre": "^1.1.0",
"express": "^4.13.4",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"redux-logger": "^2.6.1",
"webpack": "^1.12.13",
"webpack-dev-middleware": "^1.5.1",
"webpack-hot-middleware": "^2.6.4"
}
}
新增後輸入npm install
2.
webpack.config.js
var webpack = require('webpack');
module.exports = {
devtool: 'inline-source-map',
entry: [
'webpack-hot-middleware/client',
'./client/client.js'
],
output: {
path: require("path").resolve("./dist"),
filename: 'bundle.js',
publicPath: '/'
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['react', 'es2015', 'react-hmre']
}
}
]
}
}
3.
新增server 資料夾,裡面放入server.js
var express = require('express');
var path = require('path');
var config = require('../webpack.config.js');
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var app = express();
var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));
app.use(webpackHotMiddleware(compiler));
app.use(express.static('./dist'));
app.use('/', function (req, res) {
res.sendFile(path.resolve('client/index.html'));
});
var port = 3000;
app.listen(port, function(error) {
if (error) throw error;
console.log("Express server listening on port", port);
});
4.
新client資料夾,裡面放入index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Todo List</title>
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
以及,client.js
import React from 'react'
import { render } from 'react-dom'
import App from '../components/App'
import configureStore from '../redux/store'
import {Provider} from 'react-redux'
let initialState = {
todos:[{
id:0,
completed: false,
text:'initial for demo'
}]
}
let store = configureStore(initialState);
render(
<Provider store={store} >
<App/>
</Provider>,
document.getElementById('app')
)
Provider用來連結react即redux的store
5.
新增redux資料夾
裡面放入三個檔案
store.js
action.js
reducer.js
store.js
import {applyMiddleware,compose,createStore} from "redux"
import reducer from './reducer'
import logger from 'redux-logger'
let finalCreateStore = compose(
applyMiddleware(logger())
)(createStore)
export default function configureStore(initialState = { todos:[]}){
return finalCreateStore(reducer,initialState)
}
這裡我們加入了中間件logger
而store的必要參數為reducer,我們用import reducer from './reducer'
傳入
action.js
let actions ={
addTodo:(text)=>{
return ({
type:'ADD_TODO',
text:text})
}
}
export default actions
reducer.js
let getId = 1 ;
export default function reducer(state,action){
switch(action.type){
case 'ADD_TODO':
return( Object.assign({},state,{
todos:[{
text:action.text,
completed:false,
id:getId++
},...state.todos]
})
)
default:
return state;
}
}
最後建立component資料夾
裡面放入App.js
TodoInput.js
TodoList.js
App.js
import React, { Component } from 'react'
import TodoInput from './TodoInput.js'
import TodoList from './TodoList.js'
import {connect} from 'react-redux'
class App extends Component {
render() {
return (
<div>
<h1>Todo list</h1>
<TodoInput dispatch={this.props.dispatch}/>
<TodoList dispatch={this.props.dispatch} todos={this.props.todos}/>
</div>
)
}
}
function mapStateToProps(state){
return state
}
export default connect(mapStateToProps)(App)
從chrome React dev tool可以看到如果使用connect
App元件可以接到從store來的state且轉成他的props
TodoInput.js
import React, { Component } from 'react'
import action from '../redux/actions.js'
class TodoInput extends Component {
constructor(props, context) {
super(props, context)
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(){
event.preventDefault()
//this.props.dispatch()
console.log(this._input.value);
this.props.dispatch(action.addTodo(this._input.value));
}
render() {
return (
<div>
<input
type="text"
placeholder="Type in your tode"
ref={(c) => this._input = c}
/>
<button onClick={this.handleSubmit}>Submit</button>
</div>
)
}
}
export default TodoInput
TodoList.js
import React, { Component } from 'react'
class TodoList extends Component {
render() {
return (
<ul>
{
this.props.todos.map((todo)=>{
return <li key={todo.id}> {todo.text} </li>
})
}
</ul>
)
}
}
export default TodoList
完整版:(於master branch)
https://github.com/EasonWang01/Redux-tutorial
接著幫他加上toggle
使點選後判斷完成了沒,來讓事項有一橫線
1.先在action.js
let actions ={
addTodo:(text)=>{
return ({
type:'ADD_TODO',
text:text})
},
toggleTodo:(id)=>{
return({
type:'TOGGLE_TODO',
id:id,
})
}
}
export default actions
2.reducer.js
let getId = 1 ;
export default function reducer(state,action){
switch(action.type){
case 'ADD_TODO':
return( Object.assign({},state,{
todos:[{
text:action.text,
completed:false,
id:getId++
},...state.todos]
})
)
case 'TOGGLE_TODO':
return Object.assign({},state,{todos:state.todos.map(function(state){
if(state.id!==action.id){
return state
};
return {...state,completed:!state.completed}
}) }
)
default:
return state;
}
}
最後在TodoList.js
import React, { Component } from 'react'
import action from '../redux/actions.js'
import store from '../redux/store'
class TodoList extends Component {
constructor(props, context) {
super(props, context)
}
liClick(a){
store.dispatch(action.toggleTodo(a.id));
}
render() {
return (
<ul>
{
this.props.todos.map((todo)=>{
return <li
key={todo.id}
onClick={()=>this.liClick(todo)}
style= {{textDecoration:todo.completed?'line-through':'none'}}
>
{todo.text}
</li>
})
}
</ul>
)
}
}
export default TodoList
完整版在 branch toggle
接著加入三個選項,分別顯示all,active,completed
1.新增FilterLink.js
import React, { Component } from 'react'
import action from '../redux/actions.js'
import store from '../redux/store'
class FliterLink extends Component {
render(){
return <a href='#'
onClick={e=>{
e.preventDefault();
//console.log(this.props.filter)
store.dispatch(action.FilterTodo(this.props.filter))
}}
>
{this.props.children}
</a>
}
}
export default FliterLink
2.將他加入TodoList.js
import React, { Component } from 'react'
import action from '../redux/actions.js'
import store from '../redux/store'
import FilterLink from './FilterLink.js'
class TodoList extends Component {
constructor(props, context) {
super(props, context)
}
liClick(a){
store.dispatch(action.toggleTodo(a.id));
}
render() {
return (
<div>
<ul>
{
this.props.todos.map((todo)=>{
return <li
key={todo.id}
onClick={()=>this.liClick(todo)}
style= {{textDecoration:todo.completed?'line-through':'none'}}
>
{todo.text}
</li>
})
}
</ul>
<p>
{"Show: "}
<FilterLink filter="SHOW_ALL">
All
</FilterLink>
{" , "}
<FilterLink filter="SHOW_ACTIVE">
Active
</FilterLink>
{" , "}
<FilterLink filter="SHOW_COMPLETED">
Completed
</FilterLink>
</p>
</div>
)
}
}
export default TodoList
更改action.js
let actions ={
addTodo:(text)=>{
return ({
type:'ADD_TODO',
text:text})
},
toggleTodo:(id)=>{
return({
type:'TOGGLE_TODO',
id:id,
})
},
FilterTodo:(filter)=>{
return({
type:'SET_VISBILITY_FILTER',
filter:filter
})
}
}
export default actions
以及reducer.js
let getId = 1 ;
export default function reducer(state,action){
switch(action.type){
case 'ADD_TODO':
return( Object.assign({},state,{
todos:[{
text:action.text,
completed:false,
id:getId++
},...state.todos]
})
)
case 'TOGGLE_TODO':
return Object.assign({},state,{todos:state.todos.map(function(state){
if(state.id!==action.id){
return state
};
return {...state,completed:!state.completed}
}) }
)
case 'SET_VISBILITY_FILTER':
return state
default:
return state;
}
}
(此時多了三個選項,且點擊會發出action)
3.接著我們幫他加入發出action後所要做的事
我們會使用array的filter方法,過濾出state.todos中completed為false的方法
(給點擊active按鈕用)反之為給completed按鈕用
var filtered = (this.props.todos).filter(function(state){
return state.completed==false
});
console.log(filtered)
(filter()讓array中每個元素接受一個function的運算且return一個值,為true或false) (如果為true則繼續保留在array)
但
在這之前
我們先幫reducer加上一個狀態
let getId = 1 ;
export default function reducer(state,action){
switch(action.type){
case 'ADD_TODO':
return( Object.assign({},state,{
todos:[{
text:action.text,
completed:false,
id:getId++
},...state.todos]
})
)
case 'TOGGLE_TODO':
return Object.assign({},state,{todos:state.todos.map(function(state){
if(state.id!==action.id){
return state
};
return {...state,completed:!state.completed}
}) }
)
case 'SET_VISBILITY_FILTER':
return Object.assign({},state,{visbility:action.filter})
default:
return state;
}
}
接著將App.js改為
import React, { Component } from 'react'
import TodoInput from './TodoInput.js'
import TodoList from './TodoList.js'
import {connect} from 'react-redux'
class App extends Component {
render() {
return (
<div>
<h1>Todo list</h1>
<TodoInput />
<TodoList todos={this.props}/>
</div>
)
}
}
function mapStateToProp(state){
return state
}
export default connect(mapStateToProp)(App)
(因為我們現在state裡不只一個state,所以先傳入整包,於子代再做取出)
最後
(把原本傳入最後return要map的物件先做過濾)
TodoList.js
import React, { Component } from 'react'
import action from '../redux/actions.js'
import store from '../redux/store'
import FilterLink from './FilterLink.js'
class TodoList extends Component {
constructor(props, context) {
super(props, context)
}
liClick(a){
store.dispatch(action.toggleTodo(a.id));
}
render() {
var filtered = function(){
switch(this.props.todos.visbility){
case "SHOW_ALL":
return this.props.todos.todos;
case "SHOW_ACTIVE":
return (this.props.todos.todos).filter(function(state){
return state.completed==false
});
case "SHOW_COMPLETED":
return (this.props.todos.todos).filter(function(state){
return state.completed==true
});
default:
return this.props.todos.todos;
}
}.bind(this)
return (
<div>
<ul>
{
filtered().map((todo)=>{
return <li
key={todo.id}
onClick={()=>this.liClick(todo)}
style= {{textDecoration:todo.completed?'line-through':'none'}}
>
{todo.text}
</li>
})
}
</ul>
<p>
{"Show: "}
<FilterLink filter="SHOW_ALL">
All
</FilterLink>
{" , "}
<FilterLink filter="SHOW_ACTIVE">
Active
</FilterLink>
{" , "}
<FilterLink filter="SHOW_COMPLETED">
Completed
</FilterLink>
</p>
</div>
)
}
}
export default TodoList
使用bind是因為我們在function內想取得class的執行環境,所以把他綁到this
3.接著
讓點擊後的link變黑色,無法重複點擊
所以先幫FilterLink加上一個props
import React, { Component } from 'react'
import action from '../redux/actions.js'
import store from '../redux/store'
import FilterLink from './FilterLink.js'
class TodoList extends Component {
constructor(props, context) {
super(props, context)
}
liClick(a){
store.dispatch(action.toggleTodo(a.id));
}
render() {
var filtered = function(){
switch(this.props.todos.visbility){
case "SHOW_ALL":
return this.props.todos.todos;
case "SHOW_ACTIVE":
return (this.props.todos.todos).filter(function(state){
return state.completed==false
});
case "SHOW_COMPLETED":
return (this.props.todos.todos).filter(function(state){
return state.completed==true
});
default:
return this.props.todos.todos;
}
}.bind(this)
/*
var filtered = (this.props.todos).filter(function(state){
return state.completed==false
});*/
//console.log(filtered)
return (
<div>
<ul>
{
filtered(
).map((todo)=>{
return <li
key={todo.id}
onClick={()=>this.liClick(todo)}
style= {{textDecoration:todo.completed?'line-through':'none'}}
>
{todo.text}
</li>
})
}
</ul>
<p>
{"Show: "}
<FilterLink filter="SHOW_ALL"currentFilter={this.props.todos.visbility}>
All
</FilterLink>
{" , "}
<FilterLink filter="SHOW_ACTIVE"currentFilter={this.props.todos.visbility}>
Active
</FilterLink>
{" , "}
<FilterLink filter="SHOW_COMPLETED"currentFilter={this.props.todos.visbility}>
Completed
</FilterLink>
</p>
</div>
)
}
}
export default TodoList
之後點擊link後去偵測,這個link的filter props和當前store的filter屬性如符合的話,則只回傳普通的文字
import React, { Component } from 'react'
import action from '../redux/actions.js'
import store from '../redux/store'
class FliterLink extends Component {
render(){
if(this.props.currentFilter==this.props.filter){
return <span> {this.props.children}</span>
}
return <a href='#'
onClick={e=>{
e.preventDefault();
//console.log(this.props.filter)
store.dispatch(action.FilterTodo(this.props.filter))
}}
>
{this.props.children}
</a>
}
}
export default FliterLink
使用combined reduecer
在這之前,我們先改變我們reducer的寫法
let getId = 1 ;
function todos(state,action){
switch(action.type){
case 'ADD_TODO':
return [{
text:action.text,
completed:false,
id:getId++
},...state.todos]
case 'TOGGLE_TODO':
return state.todos.map(function(state){
if(state.id!==action.id){
return state
};
return {...state,completed:!state.completed}
})
default:
return state.todos;
}
}
function visbility(state,action){
switch(action.type){
case 'SET_VISBILITY_FILTER':
return action.filter
default:
return state.visbility;
}
}
function reducer (state,action){
return Object.assign({},state,{
visbility:visbility(state,action),
todos:todos(state,action)
});
}
export default reducer
(上面,我們將reducer寫成兩個function)
combined reducer即是這個概念,所以我們把reducer.js 改為如下
import { combineReducers } from 'redux'
let getId = 1 ;
function todos(state=[],action){
switch(action.type){
case 'ADD_TODO':
return [{
text:action.text,
completed:false,
id:getId++
},...state]
case 'TOGGLE_TODO':
return state.map(function(state){
if(state.id!==action.id){
return state
};
return {...state,completed:!state.completed}
})
default:
return state;
}
}
function visbility(state="SHOW_ALL",action){
switch(action.type){
case 'SET_VISBILITY_FILTER':
return action.filter
default:
return state;
}
}
const rootReducer = combineReducers({
visbility: visbility,
todos:todos
})
export default rootReducer
兩個重點
1. combineReducers 沒有丟預設state進去所以,我們要用ES6的寫法幫function寫上預設參數(Line 4 and 35)
2. function內一開始傳進去的的不再是整個state而是取key後的state,所以裡面也須更改(Line 13. 27.17.43)
最後,在ES6在物件的key跟value名稱相同時可省略
所以寫成
const rootReducer = combineReducers({
visbility,
todos,
})
在多人合作中,我們會把reducer每個function放在不同檔案,最後再import到rootReducer來減少多人一起開發功能時的conflict
中間件 BindActionCreator
將store 的dispatch 包住action 成為function 可直接使用 不用再dispatch(someaction)
App.js加上
import { bindActionCreators } from 'redux';
import actions from '../redux/actions.js'
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
即可發現點選React devtool中App的props的dispatch變為actions物件
1.
原本父元件使用this.props.dispatch
將dispatch方法傳下去,現在改為用this.props.actions
2.
而VIEW發送ACTION的方式從
handleSubmit(event) {
event.preventDefault()
this.props.dispatch(actions.addTodo(this.state.inputText))
}
改為
handleSubmit(event) {
event.preventDefault()
this.props.addTodo(this.state.inputText)
}
但一般我們不會用上面這種寫法
mapDispatchToProp
建議如下寫法
https://egghead.io/lessons/javascript-redux-using-mapdispatchtoprops-shorthand-notation
//直接把action轉為function使用,因react-redux 幫你把dispatch包在裡面了
export default connect(mapStateToProp,{
userInfoAction:actions.userInfo,
})(Login)
操作非同步動作(Async)
例如:
我們今天有一個按鈕,點擊後發出action去跟server要資料,回傳資料後再觸發一個action去更新state
使用Redux-thunk
npm install redux-thunk
將store.js改為
import {applyMiddleware,compose,createStore} from "redux"
import reducer from './reducer'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
let initialState = {
visbility:'SHOW_ALL',
todos:[{
id:0,
completed: false,
text:'initial for demo'
}]
}
let finalCreateStore = compose(
applyMiddleware(thunk,logger())
)(createStore)
function configureStore(initialState){
return finalCreateStore(reducer,initialState)
}
let store = configureStore(initialState)
export default store
之後我們action.js裡面寫的方法不只可以return物件,還可以return function
這個function就是用來寫非同步API,最後拿回資料再寫return,之後即會送到reducer處理
例如:
FilterTodo:(filter)=>{
return({
type:'SET_VISBILITY_FILTER',
filter:filter
})
},
///上面是一般的action,下面是thunk的action
con:()=>{
return (dispatch, getState)=>{
console.log(getState())
}
}
PS:
在action.js內可以直接調用store的所有方法,因為我們action是由store發出的
ex:store.dispatch(action.toggleTodo(a.id));
React-router-redux
用途:將Redux結合react-router
安裝
npm install --save react-router-redux
1.在使用combind reducer的地方加上
import {routerReducer} from 'react-router-redux'
const rootReducer = combineReducers({
visbility,
todos,
routing: routerReducer,
})
2.將client.js加上
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore} from 'react-router-redux'
const history = syncHistoryWithStore(browserHistory, store)
完整版
import React from 'react'
import { render } from 'react-dom'
import App from '../components/App'
import store from '../redux/store'
import {Provider} from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore} from 'react-router-redux'
import TodoList from '../components/TodoList.js'
import TodoInput from '../components/TodoInput.js'
const history = syncHistoryWithStore(browserHistory, store)
render(
<Provider store={store} >
<Router history={history}>
<Route path="/" foo="bar" component={App}>
<Route path="/as" component={TodoList}/>
<Route path="/ass" component={TodoInput}/>
</Route>
</Router>
</Provider>,
document.getElementById('app')
)
最後將App.js改為
import React, { Component } from 'react'
import TodoInput from './TodoInput.js'
import TodoList from './TodoList.js'
import {connect} from 'react-redux'
class App extends Component {
render() {
return (
<div>
<h1>Todo list</h1>
{React.cloneElement(this.props.children, { todos:this.props })}
</div>
)
}
}
function mapStateToProp(state){
return state
}
export default connect(mapStateToProp)(App)
使用React.cloneElement
原因為,原本react-router要顯示子帶router必須用{this.props.children}但這樣原本的prop沒辦法往下傳
所以使用React.cloneElement
即可在子代傳入props
記得加條件限制
但是,如果url為http://localhost:3000/
這時
{React.cloneElement(this.props.children, { todos:this.props })}
中的this.props.children
為null,所以會報錯
須改為
import React, { Component } from 'react'
import TodoInput from './TodoInput.js'
import TodoList from './TodoList.js'
import {connect} from 'react-redux'
class App extends Component {
checkIfRouteHasChild(props){
if(props.children!=null){
return React.cloneElement(props.children, { todos:props})
}else{
return
}};
render() {
return (
<div>
<h1>Todo list</h1>
{(()=>this.checkIfRouteHasChild(this.props))()}
</div>
)
}
}
function mapStateToProp(state){
return state
}
export default connect(mapStateToProp)(App)
Redux Devtools
1.到chrome extension 下載Redux Devtools
2.在程式的store.js中改為如下
let finalCreateStore = compose(
applyMiddleware(thunk,logger()),window.devToolsExtension ? window.devToolsExtension() : f => f
)(createStore)
function configureStore(initialState){
return finalCreateStore(reducer,initialState)
}
let store = configureStore(initialState)
export default store
3.開啟網頁即可看到chrome extension的redux devtools亮起,即可點選開啟
stateless function components
(可將class改為const)
官方推薦使用
https://facebook.github.io/react/docs/reusable-components.html#stateless-functions
https://medium.com/@joshblack/stateless-components-in-react-0-14-f9798f8b992d#.nyhbdhy1j
因為Redux 的state統一存在store中,所以符合stateless function components之條件, 可改用const來寫component
const ListOfNumbers = props => (
<ol className={props.className}>
{
props.numbers.map(number => (
<li>{number}</li>)
)
}
</ol>
);
ListOfNumbers.propTypes = {
className: React.PropTypes.string.isRequired,
numbers: React.PropTypes.arrayOf(React.PropTypes.number)
};
注意,使用const宣告的component裡面不可有state以及ref
從store 給到元件
1.
const mapStateToProp = (state) => ({
userInfo: state.userInfo
})
2.
<TextField
hintText=""
type="date"
id="date1"
value={ this.state.date }
onChange={ (e) => this.changeText(e,'date') }
floatingLabelStyle={{color: 'gray'}}
floatingLabelText=""
/>
3.
componentWillReceiveProps(nextProps) {//重新整理使用,因componentWillMount時的this.props還沒抓到
if (nextProps.userInfo !== 'undefined'){
this.setState({ date: nextProps.userInfo.birthday})
}
}
componentWillMount() {//切換元件時使用,因componentWillReceiveProps不會再切換元件時觸發,但每次切換元件state會重置
this.setState({ date: this.props.userInfo.birthday})
}
Last updated