我的函数在另一个函数范围之外无法工作,即使我已经导入了它
My function not working outside of another function scope even though I have imported it
我想弄清楚下面的代码是怎么回事,我已经使用 import { getTodos } from './todos'
将我的函数导入到另一个名为 views.js
的文件中。我试图在 views.js
的顶层调用 getTodos
,但我得到一个错误,它不是一个函数(它是 undefined
)。但是一旦我尝试在同一个模块的函数中使用 getTodos
,它突然起作用了!?为什么它突然不再是 undefined 了,我怎样才能修复代码以便它定义在我想要的位置?
我的 todos.js 文件,其中包括定义 getTodos 函数:
import uuidv4 from 'uuid/v4'
import { renderTodos } from './views'
let todos = []
const loadTodos = () => {
const todosJSON = localStorage.getItem('todos')
try {
return todosJSON ? JSON.parse(todosJSON) : []
} catch (e) {
return []
}
}
todos = loadTodos()
const getTodos = () => todos
const saveTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos))
}
const createTodo = (text) => {
if (text.length > 0) {
todos.push({
id: uuidv4(),
text,
completed: false
})
saveTodos()
renderTodos()
}
}
const removeTodo = (id) => {
const todoIndex = todos.findIndex((todo) => todo.id === id)
if (todoIndex > -1) {
todos.splice(todoIndex, 1)
saveTodos()
}
}
const toggleTodo = (id) => {
const todo = todos.find((todo) => todo.id === id)
if (todo) {
todo.completed = !todo.completed
saveTodos()
}
}
export { loadTodos, getTodos, createTodo, removeTodo, toggleTodo }
我的 views.js 文件,我试图在其中加载 getTodos 然后使用它:
import { getTodos, saveTodos, toggleTodo, removeTodo } from './todos'
import { getFilters } from './filters'
const todos = getTodos()
const renderTodos = () => {
const filters = getFilters()
const todosEl = document.querySelector('#todos')
const filteredTodos = todos.filter((todo) => {
const searchTextMatch = todo.text.toLowerCase().includes(filters.searchText.toLowerCase())
const hideCompletedMatch = !filters.hideCompleted || !todo.completed
return searchTextMatch && hideCompletedMatch
})
const incompleteTodos = filteredTodos.filter((todo) => !todo.completed)
todosEl.innerHTML = ''
todosEl.appendChild(generateSummaryDOM(incompleteTodos))
if (todos.length > 0) {
filteredTodos.forEach((todo) => {
todosEl.appendChild(generateTodoDOM(todo))
})
} else {
const emptyMessage = document.createElement('p')
emptyMessage.textContent = 'No to-dos to show, go ahead and add some!'
emptyMessage.classList.add('empty-message')
todosEl.appendChild(emptyMessage)
}
}
const generateTodoDOM = (todo) => {
const todoEl = document.createElement('label')
const containerEl = document.createElement('div')
const checkbox = document.createElement('input')
const removeButton = document.createElement('button')
const span = document.createElement('span')
// Setup Container
todoEl.classList.add('list-item')
containerEl.classList.add('list-item__container')
todoEl.appendChild(containerEl)
// Setup Remove Button
removeButton.textContent = 'remove'
removeButton.classList.add('button', 'button--text')
removeButton.addEventListener('click', () => {
removeTodo(todo.id)
renderTodos()
})
span.textContent = todo.text
// Setup Checkbox
checkbox.setAttribute('type', 'checkbox')
checkbox.checked = todo.completed
checkbox.addEventListener('change', (e) => {
toggleTodo(todo.id)
renderTodos()
})
containerEl.appendChild(checkbox)
containerEl.appendChild(span)
todoEl.appendChild(removeButton)
return todoEl
}
const generateSummaryDOM = (incompleteTodos) => {
const summary = document.createElement('h2')
summary.classList.add('list-title')
const plural = incompleteTodos.length === 1 ? '' : 's'
summary.textContent = `You have ${incompleteTodos.length} todo${plural} left`
return summary
}
// Make sure to set up the exports
export { generateSummaryDOM, renderTodos, generateTodoDOM }
index.js 文件:
import { createTodo } from './todos'
import { renderTodos } from './views'
import { setFilters } from './filters'
// Render initial todos
renderTodos()
// Set up search text handler
document.querySelector('#search-todos').addEventListener('input', (e) => {
setFilters({
searchText: e.target.value
})
renderTodos()
})
// Set up checkbox handler
document.querySelector('#hide-completed').addEventListener('change', (e) => {
setFilters({
hideCompleted: e.target.checked
})
renderTodos()
})
// Set up form submission handler
document.querySelector('#add-todo').addEventListener('submit', (e) => {
e.preventDefault()
const text = e.target.elements.addTodoText.value.trim()
createTodo(text)
e.target.elements.addTodoText.value = ''
})
我得到的 chrome 调试器控制台中的错误是:
views.js:4 Uncaught TypeError: (0 , _todos.getTodos) is not a function
at Object../src/views.js (views.js:4)
at __webpack_require__ (bootstrap:19)
at Object../src/todos.js (todos.js:2)
at __webpack_require__ (bootstrap:19)
at Object../src/index.js (index.js:1)
at __webpack_require__ (bootstrap:19)
at Object.0 (bundle.js:20254)
at __webpack_require__ (bootstrap:19)
at bootstrap:68
at bootstrap:68
我只是想了解为什么会发生这种奇怪的行为,因为一旦我在 renderTodos
中使用 getTodos,它就可以正常工作并且我没有收到任何错误,如下所示:
const renderTodos = () => {
const todos = getTodos()
// Other stuffs
}
我正在使用 Babel 和 Webpack。
主要问题是你有一个循环依赖。对于可以说明问题的更小的示例,请考虑:
// foo.js
import bar from './bar';
const x = bar();
export default () => x;
// bar.js
import foo from './foo';
const y = foo();
export default () => y;
在上面的代码中,就像在您的代码中一样,您有使用导入的模块,其中所述导入 also 取决于从当前模块导入某些内容。如果 A 从 B 导入,B 也从 A 导入,那么您需要确保 A 和 B 在他们的顶级代码完全完成之前都没有使用彼此的任何东西 - 否则,您不能依赖当时正在定义其他导入。
最好重构代码以某种方式消除循环依赖。
我喜欢使用的一种模式是确保模块中的顶级代码不会启动任何东西 - 相反,当一切最终都可以从 入口点[=52= 开始时,这很好](这里看起来是 index.js
)。一个快速的修复方法是为每个模块导出一个运行初始化代码的 function,而不是将该代码放在顶层。例如:
// views.js
import { getTodos, saveTodos, toggleTodo, removeTodo } from './todos'
import { getFilters } from './filters'
let todos;
const init = () => {
todos = getTodos();
};
// ... everything else
export { generateSummaryDOM, renderTodos, generateTodoDOM, init }
然后让 index.js
导入并在导入所有内容后立即调用 init
。
幸运的是,由于 todos.js
只在其 createTodo
函数中调用(跨模块,导入) renderTodos
,并且 createTodo
被导出但未在 todos.js
,不用改。
另一个(可能更好)选项是移除 todos.js
对 renderTodos
的依赖。在 todos.js
中,您目前仅在其 createTodo
函数中使用 renderTodos
。考虑更改 thigns 以便 createTodo
创建并保存待办事项,但不使用 renderTodos
呈现它 - 相反,查找使用 createTodo
的位置(看起来仅在 index.js
),并让 index.js
改为调用 renderTodos
:
// todos.js
// do not import renderTodos here
// ...
const createTodo = (text) => {
if (text.length > 0) {
todos.push({
id: uuidv4(),
text,
completed: false
})
saveTodos()
}
}
和
// index.js
// ...
document.querySelector('#add-todo').addEventListener('submit', (e) => {
e.preventDefault()
const text = e.target.elements.addTodoText.value.trim()
createTodo(text)
renderTodos();
e.target.elements.addTodoText.value = ''
})
我想弄清楚下面的代码是怎么回事,我已经使用 import { getTodos } from './todos'
将我的函数导入到另一个名为 views.js
的文件中。我试图在 views.js
的顶层调用 getTodos
,但我得到一个错误,它不是一个函数(它是 undefined
)。但是一旦我尝试在同一个模块的函数中使用 getTodos
,它突然起作用了!?为什么它突然不再是 undefined 了,我怎样才能修复代码以便它定义在我想要的位置?
我的 todos.js 文件,其中包括定义 getTodos 函数:
import uuidv4 from 'uuid/v4'
import { renderTodos } from './views'
let todos = []
const loadTodos = () => {
const todosJSON = localStorage.getItem('todos')
try {
return todosJSON ? JSON.parse(todosJSON) : []
} catch (e) {
return []
}
}
todos = loadTodos()
const getTodos = () => todos
const saveTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos))
}
const createTodo = (text) => {
if (text.length > 0) {
todos.push({
id: uuidv4(),
text,
completed: false
})
saveTodos()
renderTodos()
}
}
const removeTodo = (id) => {
const todoIndex = todos.findIndex((todo) => todo.id === id)
if (todoIndex > -1) {
todos.splice(todoIndex, 1)
saveTodos()
}
}
const toggleTodo = (id) => {
const todo = todos.find((todo) => todo.id === id)
if (todo) {
todo.completed = !todo.completed
saveTodos()
}
}
export { loadTodos, getTodos, createTodo, removeTodo, toggleTodo }
我的 views.js 文件,我试图在其中加载 getTodos 然后使用它:
import { getTodos, saveTodos, toggleTodo, removeTodo } from './todos'
import { getFilters } from './filters'
const todos = getTodos()
const renderTodos = () => {
const filters = getFilters()
const todosEl = document.querySelector('#todos')
const filteredTodos = todos.filter((todo) => {
const searchTextMatch = todo.text.toLowerCase().includes(filters.searchText.toLowerCase())
const hideCompletedMatch = !filters.hideCompleted || !todo.completed
return searchTextMatch && hideCompletedMatch
})
const incompleteTodos = filteredTodos.filter((todo) => !todo.completed)
todosEl.innerHTML = ''
todosEl.appendChild(generateSummaryDOM(incompleteTodos))
if (todos.length > 0) {
filteredTodos.forEach((todo) => {
todosEl.appendChild(generateTodoDOM(todo))
})
} else {
const emptyMessage = document.createElement('p')
emptyMessage.textContent = 'No to-dos to show, go ahead and add some!'
emptyMessage.classList.add('empty-message')
todosEl.appendChild(emptyMessage)
}
}
const generateTodoDOM = (todo) => {
const todoEl = document.createElement('label')
const containerEl = document.createElement('div')
const checkbox = document.createElement('input')
const removeButton = document.createElement('button')
const span = document.createElement('span')
// Setup Container
todoEl.classList.add('list-item')
containerEl.classList.add('list-item__container')
todoEl.appendChild(containerEl)
// Setup Remove Button
removeButton.textContent = 'remove'
removeButton.classList.add('button', 'button--text')
removeButton.addEventListener('click', () => {
removeTodo(todo.id)
renderTodos()
})
span.textContent = todo.text
// Setup Checkbox
checkbox.setAttribute('type', 'checkbox')
checkbox.checked = todo.completed
checkbox.addEventListener('change', (e) => {
toggleTodo(todo.id)
renderTodos()
})
containerEl.appendChild(checkbox)
containerEl.appendChild(span)
todoEl.appendChild(removeButton)
return todoEl
}
const generateSummaryDOM = (incompleteTodos) => {
const summary = document.createElement('h2')
summary.classList.add('list-title')
const plural = incompleteTodos.length === 1 ? '' : 's'
summary.textContent = `You have ${incompleteTodos.length} todo${plural} left`
return summary
}
// Make sure to set up the exports
export { generateSummaryDOM, renderTodos, generateTodoDOM }
index.js 文件:
import { createTodo } from './todos'
import { renderTodos } from './views'
import { setFilters } from './filters'
// Render initial todos
renderTodos()
// Set up search text handler
document.querySelector('#search-todos').addEventListener('input', (e) => {
setFilters({
searchText: e.target.value
})
renderTodos()
})
// Set up checkbox handler
document.querySelector('#hide-completed').addEventListener('change', (e) => {
setFilters({
hideCompleted: e.target.checked
})
renderTodos()
})
// Set up form submission handler
document.querySelector('#add-todo').addEventListener('submit', (e) => {
e.preventDefault()
const text = e.target.elements.addTodoText.value.trim()
createTodo(text)
e.target.elements.addTodoText.value = ''
})
我得到的 chrome 调试器控制台中的错误是:
views.js:4 Uncaught TypeError: (0 , _todos.getTodos) is not a function
at Object../src/views.js (views.js:4)
at __webpack_require__ (bootstrap:19)
at Object../src/todos.js (todos.js:2)
at __webpack_require__ (bootstrap:19)
at Object../src/index.js (index.js:1)
at __webpack_require__ (bootstrap:19)
at Object.0 (bundle.js:20254)
at __webpack_require__ (bootstrap:19)
at bootstrap:68
at bootstrap:68
我只是想了解为什么会发生这种奇怪的行为,因为一旦我在 renderTodos
中使用 getTodos,它就可以正常工作并且我没有收到任何错误,如下所示:
const renderTodos = () => {
const todos = getTodos()
// Other stuffs
}
我正在使用 Babel 和 Webpack。
主要问题是你有一个循环依赖。对于可以说明问题的更小的示例,请考虑:
// foo.js
import bar from './bar';
const x = bar();
export default () => x;
// bar.js
import foo from './foo';
const y = foo();
export default () => y;
在上面的代码中,就像在您的代码中一样,您有使用导入的模块,其中所述导入 also 取决于从当前模块导入某些内容。如果 A 从 B 导入,B 也从 A 导入,那么您需要确保 A 和 B 在他们的顶级代码完全完成之前都没有使用彼此的任何东西 - 否则,您不能依赖当时正在定义其他导入。
最好重构代码以某种方式消除循环依赖。
我喜欢使用的一种模式是确保模块中的顶级代码不会启动任何东西 - 相反,当一切最终都可以从 入口点[=52= 开始时,这很好](这里看起来是 index.js
)。一个快速的修复方法是为每个模块导出一个运行初始化代码的 function,而不是将该代码放在顶层。例如:
// views.js
import { getTodos, saveTodos, toggleTodo, removeTodo } from './todos'
import { getFilters } from './filters'
let todos;
const init = () => {
todos = getTodos();
};
// ... everything else
export { generateSummaryDOM, renderTodos, generateTodoDOM, init }
然后让 index.js
导入并在导入所有内容后立即调用 init
。
幸运的是,由于 todos.js
只在其 createTodo
函数中调用(跨模块,导入) renderTodos
,并且 createTodo
被导出但未在 todos.js
,不用改。
另一个(可能更好)选项是移除 todos.js
对 renderTodos
的依赖。在 todos.js
中,您目前仅在其 createTodo
函数中使用 renderTodos
。考虑更改 thigns 以便 createTodo
创建并保存待办事项,但不使用 renderTodos
呈现它 - 相反,查找使用 createTodo
的位置(看起来仅在 index.js
),并让 index.js
改为调用 renderTodos
:
// todos.js
// do not import renderTodos here
// ...
const createTodo = (text) => {
if (text.length > 0) {
todos.push({
id: uuidv4(),
text,
completed: false
})
saveTodos()
}
}
和
// index.js
// ...
document.querySelector('#add-todo').addEventListener('submit', (e) => {
e.preventDefault()
const text = e.target.elements.addTodoText.value.trim()
createTodo(text)
renderTodos();
e.target.elements.addTodoText.value = ''
})