I'm learning Redux with Dan Abramov's Redux course videos on http://egghead.io .
I had this code, working fine:
export const todoApp = (state = {}, action) => {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
}
}
export const todos = (state = [], action) => {
// ...implementation
};
const visibilityFilter = (state = "SHOW_ALL", action) => {
// ...implementation
}
Then I tried to replace the todoApp
reducer function with one generated with the combineReducers
helper function:
import { combineReducers } from 'redux'
export const todoApp = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter,
});
export const todos = (state = [], action) => {
// ...implementation
};
const visibilityFilter = (state = "SHOW_ALL", action) => {
// ...implementation
}
But when I try to run the unit tests (which were working fine) I get this error:
ReferenceError: todos is not defined
However, if I cut and copy the todoApp
function definition to the bottom of the file (like in the code below), everything works fine again!
import { combineReducers } from 'redux'
export const todos = (state = [], action) => {
// ...implementation
};
const visibilityFilter = (state = "SHOW_ALL", action) => {
// ...implementation
}
export const todoApp = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter,
});
I have also tried to place the todoApp
definition between the todos
and visibilityFilter
functions. In that case, I get the error
ReferenceError: visibilityFilter is not defined
So if I create the todoApp
reducer function myself at the top of the file everything works fine, regardless of the function declaration order, but if I use the combineReducers
helper function, I need to do it under all the other declarations.
Can somebody explain me why is this happening and what are the good practices to be followed concerning the declaration order?
This has to do with ECMAScript's way of evaluation and initialization. In your first snippet, where todoApp
shows up before the other functions:
export const todoApp = (state = {}, action) => {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
}
}
The reason why this does not throw a ReferenceError is because the return value is evaluated after todos
and visibilityFilter
is defined. This is described in the ECMAScript 2018 Specification :
9.2.12 FunctionDeclarationInstantiation ( func , argumentsList )
NOTE 1 [...] All other bindings are initialized during evaluation of the function body.
Here, the specification notes that when FunctionDeclarationInstantiation is called (which is called when you create a new function such as todoApp
, the bindings (such as variables or return values) are evaluated during the evaluation of the function body. This implies that the function body is not evaluated when the function is created thus the return value object is not known to contain a reference to todos
or visibilityFilter
which does not exist yet. This is is also reinforced in Object Initializers :
12.2.6 Object Initializer
NOTE 1 An object initializer is an expression describing the initialization of an Object, written in a form resembling a literal. It is a list of zero or more pairs of property keys and associated values, enclosed in curly brackets. The values need not be literals; they are evaluated each time the object initializer is evaluated.
The last line mentions that the values of an object are not evaluated until the object itself is evaluated.
Thus, since the return value of todoApp
is not evaluated until you actually call todoApp
, and object values are not evaluated until the return value is evaluated, no ReferenceError is reported because the todos
and visibilityFilter
references are not evaluated .
On the contrary, your second example is evaluated before todos
and visibilityFilter
are defined:
export const todoApp = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter,
});
Here, combineReducers
is a function call. Because todos
and visibilityFilter
are function expressions , they are not hoisted, thus the combineReducers
call is evaluated before they exist. The object initializer is evaluated because it's an argument to combineReducers
, and once the initializer is evaluated, the values are evaluated. The reference to todos
and visibilityFilter
give a ReferenceError because they do not exist yet.
This also explains the third example as it works because combineReducers
is called after todos
and visibilityFilter
are defined. Your final attempt at putting todosApp
between todos
and visibilityFilter
raises another ReferenceError because todos
will be evaluated and exist but visibilityFilter
will not because it is not hoisted.
Yes. If you use the function
keyword, the declaration is "hoisted" to the top of the scope, in the same way that the var
keyword works.
If you use const
or let
, then the variable doesn't actually "exist" until the line that it's declared on, and is undefined
above that. (The technical term for this is the "Temporal Dead Zone".)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.