class: middle center
class: middle center
layout: true
props => <Component {...props} />
...
data => <Application {...data} />
const TodoMVC = props => (
<Container>
<Header>
<Filters />
</Header>
<TodoList>
<TodoItem>
<Status />
<Text />
<TextEditor />
</TodoItem>
{...}
</TodoList>
<Footer />
</Container>
)
???
- Dynamic use input
- Shared data cross components
class: middle layout: false
- State Management
- Predictable State
layout: true
data => <Application {...data} />
||
﹀
(data <=> <Application />) <=> User
||
﹀
State -----> View(Component)
︿ /
\ /
\ ﹀
User
import { createStore } from 'redux'
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const store = createStore(counterReducer)
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch({ type: 'INCREMENT' })
- Integrate Redux with React
- Do not use any other tools rather than React or Redux
- Add setInterval to dispatch 'INCREMENT' action
- Provider
- connect
import { Provider as Redux, connect } from 'react-redux'
const initialState = { count: 0 }
const store = createStore(counterReducer, initialState)
const App = props => <div>Redux Counter: {props.count}</div>
const ConnectedApp = connect(state => state)(App)
ReactDom.render(
<Redux store={store}>
<ConnectedApp />
</Redux>
, rootDom)
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const incrementAction = { type: 'INCREMENT' }
const store = createStore(counterReducer)
store.dispatch(incrementAction)
class: middle center
layout: true
let x = 1 + 2;
let foo = () => 1 + 2;
const incrementAction = { type: 'INCREMENT' }
const incrementActionThunk = () => ({ type: 'INCREMENT' })
const incrementActionAsyncThunk = (dispatch) => {
setTimeout(() => {
dispatch(incrementAction)
}, 1000)
}
const incrementIfOddActionAsyncThunk = (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 !== 0) {
dispatch(incrementAction)
}
}
layout: true
({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
({ dispatch, getState }) => {
return next => {
return action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
}
}
- Create Async action with thunk
- A component will trigger an action every 2 seconds from componentDidMount
- This action will dispatch another increment action after 2 seconds
- Create a simple logger redux middleware
console.log
every action with action type
- https://github.com/evgenyrodionov/redux-logger
- https://github.com/gaearon/redux-devtools
- https://github.com/zalmoxisus/redux-devtools-extension
import { createStore, applyMiddleware, compose } from 'redux'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
const DevTool = createDevTools(
<DockMonitor toggleVisibilityKey='ctrl-h'
changePositionKey='ctrl-q'
changeMonitorKey='ctrl-m'>
<LogMonitor />
</DockMonitor>
)
const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(thunk), DevTools.instrument())
)
render(
<Provider store={store}>
<div><TodoApp /><DevTools /></div>
</Provider>
document.getElementById('app')
)
layout: false
const increment = () => { type: INCREMENT }
const incrementTwiceWith = (x) => {
type: INCREMENT_TWICE_WITH,
payload: x * 2
}
const reducer = (state, action) => ({
switch (action.type) {
case INCREMENT:
return state + 1
case INCREMENT_TWICE_WITH:
return state + action.payload
default:
return state
}
})
import { createAction, handleActions } from 'redux-actions'
const increment = createAction(INCREMENT)
const incrementTwiceWith = createAction(INCREMENT_TWICE_WITH, x => x * 2)
const reducer = handleActions({
[INCREMENT]: state => state + 1,
[INCREMENT_TWICE_WITH]: (state, action) => state + action.payload * 2
})
Refactor your practice with the logger
and redux-action
import { createAction, handleActions } from 'redux-actions'
const increment = createAction(INCREMENT)
const incrementTwiceWith = createAction(INCREMENT_TWICE_WITH, x => x * 2)
const reducer = handleActions({
[INCREMENT]: state => state + 1,
[INCREMENT_TWICE_WITH]: (state, action) => state + action.payload * 2
})
import { createStore, applyMiddleware, compose } from 'redux'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
const DevTool = createDevTools(
<DockMonitor toggleVisibilityKey='ctrl-h'
changePositionKey='ctrl-q'
changeMonitorKey='ctrl-m'>
<LogMonitor />
</DockMonitor>
)
const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(thunk), DevTools.instrument())
)
layout: true
const incrementActionThunk = () => ({ type: 'INCREMENT' })
const reducer = (state, action) => ({
if (action.type === 'INCREMENT') {
state = state + 1
}
})
const updateCount = (dispatch, getState) => ({
type: 'INCREMENT',
payload: getState().count + 1
})
const reducer = (state, action) => ({
if (action.type === 'INCREMENT') {
state = action.payload
}
})
??? Complex action or Complex reducer
- Event system disadvantage
- Database driven design
const increment = () => { type: INCREMENT }
const incrementTwiceWith = (x) => {
type: INCREMENT_TWICE_WITH,
payload: x * 2
}
const incrementWith = x => {
type: INCREMENT_WITH,
payload: x
}
const increment = () => dispatch => dispatch(incrementWith(1))
const incrementTwiceWith = (x) => dispatch => dispatch(incrementWith(x * 2))
const initialState = {
user: {
id: null
},
isLoading: false
}
const login = (username, password) => (dispatch, getState) => {
const { isLoading } = getState()
if (!isLoading) {
dispatch(loading(true))
}
API.login({ username, password }).then(user => {
dispatch(loading(false))
dispatch(user ? loginSuccess(user) : loginFail())
}).catch(e => {
dispatch(loading(false))
loginFail(e)
})
}
layout: false
- Refactor previous Practice with
redux-actions
- Add waiting status for async increment
- An async increment button to trigger increment in 1 second
- While waiting for async increment, show waiting
- When increment done, remove waiting
layout: true
const INCREMENT_ASYNC = 'INCREMENT_ASYNC'
const incrementAsync = createAction(INCREMENT_ASYNC)
const incrementAsyncSaga = function* incrementAsync() {
return new Promise((res, rej) => {
// Or do something else async
setTimeout(() => res(), 1000)
})
}
const rootSaga = function* root() {
yield [
takeLatest(INCREMENT_ASYNC, incrementAsyncSaga)
]
}
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(thunk, sagaMiddleware),
initialState
)
sagaMiddleware.run(rootSaga)
Refactor previous Practice Complex Action into Saga
layout: false class: center middle
.left[
- Map and manage your state to Redux
- Save your state in localStorage
- Use Saga to operate the localStorage ]
class: center middle