过年之前看了FCC上的Redux,做了习题。
Redux
Create a redux state
Redux is a state management framework that can be used with a number of different web technologies, including React.
In Redux, there is a single state object that’s responsible for the entire state of your application. This means if you had a React app with ten components, and each component had its own local state, the entire state of your app would be defined by a single state object housed in the Redux
store
the Redux store is the single source of truth when it comes to application state.
This also means that any time any piece of your app wants to update state, it must do so through the Redux store.
The Redux
store
is an object which holds and manages applicationstate
. There is a method calledcreateStore()
on the Redux object, which you use to create the Reduxstore
1
2
3
4
5
6
7
8
9const reducer = (state = 5) => {
return state;
}
// Redux methods are available from a Redux object
// For example: Redux.createStore()
// Define the store here:
const store = Redux.createStore(reducer)
Get state from the redux store
The Redux store object provides several methods that allow you to interact with it. For example, you can retrieve the current
state
held in the Redux store object with thegetState()
method.1
2
3
4
5
6const store = Redux.createStore(
(state = 5) => state
);
// change code below this line
const currentState = store.getState()
Define a redux action
In Redux, all state updates are triggered by dispatching actions
An action is simply a JavaScript object that contains information about an action event that has occurred. The Redux store receives these action objects, then updates its state accordingly
1
2// Define an action here:
const action = {type: 'LOGIN'}
Define an action creator
An action creator is simply a JavaScript function that returns an action. In other words, action creators create objects that represent action events.
1
2
3
4
5const action = {
type: 'LOGIN'
}
// Define an action creator here:
const actionCreator = () => action
Dispatch an action event
dispatch
method is what you use to dispatch actions to the Redux storeCalling
store.dispatch()
and passing the value returned from an action creator sends an action back to the store.the following lines are equivalent, and both dispatch the action of type
LOGIN
1
2store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });1
2
3
4
5
6
7
8
9
10
11
12const store = Redux.createStore(
(state = {login: false}) => state
);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
// Dispatch the action here:
store.dispatch(loginAction())
Handle an action in the store
After an action is created and dispatched, the Redux store needs to know how to respond to that action. This is the job of a
reducer
functionA
reducer
takesstate
andaction
as arguments, and it always returns a newstate
It is important to see that this is the only role of the reducer. It has no side effects — it never calls an API endpoint and it never has any hidden surprises. The reducer is simply a pure function that takes state and action, then returns new state.
Another key principle in Redux is that
state
is read-only. In other words, thereducer
function must always return a new copy ofstate
and never modify state directly.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const defaultState = {
login: false
};
const reducer = (state = defaultState, action) => {
// change code below this line
if(action.type === 'LOGIN') {
return { login: true }
} else {
return defaultState
}
// change code above this line
};
const store = Redux.createStore(reducer);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
Use a switch statement to handle multiple actions
1 | const defaultState = { |
User const for action types
A common practice when working with Redux is to assign action types as read-only constants, then reference these constants wherever they are used
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44// change code below this line
const LOGIN = 'LOGIN'
const LOGOUT = 'LOGOUT'
// change code above this line
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
switch (action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: LOGIN
}
};
const logoutUser = () => {
return {
type: LOGOUT
}
};
Register a store listener
Another method you have access to on the Redux
store
object isstore.subscribe()
. This allows you to subscribe listener functions to the store, which are called whenever an action is dispatched against the store1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const ADD = 'ADD';
const reducer = (state = 0, action) => {
switch(action.type) {
case ADD:
return state + 1;
default:
return state;
}
};
const store = Redux.createStore(reducer);
// global count variable:
let count = 0;
// change code below this line
store.subscribe(() => count ++ )
// change code above this line
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
Combine multiple reducers
When the state of your app begins to grow more complex, it may be tempting to divide state into multiple pieces. Instead, remember the first principle of Redux: all app state is held in a single state object in the store.
ou define multiple reducers to handle different pieces of your application’s state, then compose these reducers together into one root reducer. The root reducer is then passed into the Redux
createStore()
method.In order to let us combine multiple reducers together, Redux provides the
combineReducers()
method. This method accepts an object as an argument in which you define properties which associate keys to specific reducer functions1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const counterReducer = (state = 0, action) => {
switch(action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';
const authReducer = (state = {authenticated: false}, action) => {
switch(action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const rootReducer = Redux.combineReducers({
count: counterReducer,
auth: authReducer,
})// define the root reducer here
const store = Redux.createStore(rootReducer);
Send action data to the store
1 | const ADD_NOTE = 'ADD_NOTE'; |
Use middleware to handle asynchronous actions
At some point you’ll need to call asynchronous endpoints in your Redux app, so how do you handle these types of requests? Redux provides middleware designed specifically for this purpose, called Redux Thunk middleware
To include Redux Thunk middleware, you pass it as an argument to
Redux.applyMiddleware()
. This statement is then provided as a second optional parameter to thecreateStore()
function.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'
const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }
const handleAsync = () => {
return function(dispatch) {
// dispatch request action here
store.dispatch(requestingData())
setTimeout(function() {
let data = {
users: ['Jeff', 'William', 'Alice']
}
// dispatch received data action here
store.dispatch(receivedData(data))
}, 2500);
}
};
const defaultState = {
fetching: false,
users: []
};
const asyncDataReducer = (state = defaultState, action) => {
switch(action.type) {
case REQUESTING_DATA:
return {
fetching: true,
users: []
}
case RECEIVED_DATA:
return {
fetching: false,
users: action.users
}
default:
return state;
}
};
const store = Redux.createStore(
asyncDataReducer,
Redux.applyMiddleware(ReduxThunk.default)
);
Write a counter with redux
1 | const INCREMENT = 'INCREMENT'; // define a constant for increment action types |
Never mutate state
Immutable state means that you never modify state directly, instead, you return a new copy of state.
If you took a snapshot of the state of a Redux app over time, you would see something like
state 1
,state 2
,state 3
,state 4
,...
and so on where each state may be similar to the last, but each is a distinct piece of data.This immutability, in fact, is what provides such features as time-travel debugging that you may have heard about.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33const ADD_TO_DO = 'ADD_TO_DO';
// A list of strings representing tasks to do:
const todos = [
'Go to the store',
'Clean the house',
'Cook dinner',
'Learn to code',
];
const immutableReducer = (state = todos, action) => {
switch(action.type) {
case ADD_TO_DO:
// don't mutate state here or the tests will fail
return [
...todos,
action.todo,
]
default:
return state;
}
};
// an example todo argument would be 'Learn React',
const addToDo = (todo) => {
return {
type: ADD_TO_DO,
todo
}
}
const store = Redux.createStore(immutableReducer);
Use the spread operator on arrays
One solution from ES6 to help enforce state immutability in Redux is the spread operator:
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const immutableReducer = (state = ['Do not mutate state!'], action) => {
switch(action.type) {
case 'ADD_TO_DO':
// don't mutate state here or the tests will fail
return [...state, action.todo]
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: 'ADD_TO_DO',
todo
}
}
const store = Redux.createStore(immutableReducer);
Remove an item from an array
1 | const immutableReducer = (state = [0,1,2,3,4,5], action) => { |
Copy an object with Object.assign
Object.assign()
takes a target object and source objects and maps properties from the source objects to the target objectAny matching properties are overwritten by properties in the source objects
This behavior is commonly used to make shallow copies of objects by passing an empty object as the first argument followed by the object(s) you want to copy.
const newObject = Object.assign({}, obj1, obj2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const defaultState = {
user: 'CamperBot',
status: 'offline',
friends: '732,982',
community: 'freeCodeCamp'
};
const immutableReducer = (state = defaultState, action) => {
switch(action.type) {
case 'ONLINE':
// don't mutate state here or the tests will fail
return Object.assign({}, defaultState, {status: 'online'})
return
default:
return state;
}
};
const wakeUp = () => {
return {
type: 'ONLINE'
}
};
const store = Redux.createStore(immutableReducer);