我正在使用Dan Abramov's Redux course videos on http://egghead.io学习Redux。
我有这个代码,工作正常:
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
}
然后我尝试将todoApp
reducer函数替换为使用combineReducers
辅助函数生成的函数:
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
}
但是当我尝试运行单元测试(工作正常)时,我收到了这个错误:
ReferenceError: todos is not defined
但是,如果我将todoApp
函数定义剪切并复制到文件的底部(如下面的代码中所示),那么一切都可以正常工作了!
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,
});
我还尝试在todoApp
和todos
函数之间放置visibilityFilter
定义。在那种情况下,我收到错误
ReferenceError: visibilityFilter is not defined
因此,如果我在文件顶部自己创建todoApp
reducer函数,无论函数声明顺序如何,一切正常,但如果我使用combineReducers
辅助函数,我需要做它在所有其他声明之下。
有人可以解释一下为什么会发生这种情况以及有关申报令的良好做法是什么?
答案 0 :(得分:1)
这与ECMAScript的评估和初始化方式有关。在您的第一个代码段中,todoApp
显示在其他功能之前:
export const todoApp = (state = {}, action) => {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
}
}
这不会引发ReferenceError的原因是因为在 todos
和visibilityFilter
定义之后评估了返回值。这在ECMAScript 2018 Specification:
9.2.12 FunctionDeclarationInstantiation( func , argumentsList )
注1 [...]在评估函数体时初始化所有其他绑定。
这里,规范指出当调用FunctionDeclarationInstantiation时(在创建todoApp
之类的新函数时调用它,期间评估绑定(如变量或返回值) >函数体的评估。这意味着在创建函数时函数体不评估,因此不知道返回值对象包含对todos
或{{的引用1}}现在还不存在。Object Initializers:
12.2.6对象初始化程序
注1 对象初始值设定项是一个描述Object初始化的表达式,以类似于文字的形式编写。它是零个或多个属性键和关联值对的列表,用大括号括起来。价值观不一定是文字;每次评估对象初始值设定项时都会对它们进行评估。
最后一行提到在评估对象本身之前不会评估对象的值。
因此,由于在实际调用visibilityFilter
之前不评估todoApp
的返回值,并且在评估返回值之前不评估对象值,因此不会报告ReferenceError,因为todoApp
和todos
引用未经过评估。
相反,在定义visibilityFilter
和todos
之前评估您的第二个示例:
visibilityFilter
这里,export const todoApp = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter,
});
是函数调用。由于combineReducers
和todos
是函数表达式,因此它们不会被挂起,因此visibilityFilter
调用会在它们存在之前进行评估。对象初始值设定项进行求值,因为它是combineReducers
的参数,并且一旦初始化程序被计算,就会对值进行求值。对combineReducers
和todos
的引用会产生ReferenceError,因为它们尚不存在。
这也解释了第三个示例,因为它是有效的,因为在{/ em> visibilityFilter
和combineReducers
之后todos
被称为。您将visibilityFilter
置于todosApp
和todos
之间的最后尝试会引发另一个ReferenceError,因为visibilityFilter
将被评估并存在,但todos
将不会因为它未被提升而存在。
答案 1 :(得分:-2)
是。如果您使用function
关键字,则声明会“悬挂”到范围顶部,与var
关键字的工作方式相同。
如果您使用const
或let
,那么变量实际上并不“存在”,直到它声明的行,并且高于undefined
。 (技术术语是“时间死区”。)