连接数据
到目前为止,我们创建了隔离的无状态组件——这对 Storybook 来说很棒,但除非我们在应用程序中为它们提供一些数据,否则最终是没有用的。
本教程不关注构建应用程序的细节,因此我们不会在此处深入探讨这些细节。但是,我们将花一点时间来了解使用容器组件连接数据的常见模式。
加载数据
使用 Ember,您可以使用多种方法加载数据,但有两件事您需要记住
- 数据加载的位置
- 数据加载的方式
默认情况下,您应该使用路由和数据持久层,例如 ember-data 或 Apollo。对于我们的小示例,我们将使用 tracked-redux 来演示如何将其与路由一起使用。
使用以下命令将必要的依赖项添加到您的项目中
ember install tracked-redux
首先,我们将构建一个简单的 Redux 存储,它响应更改任务状态的操作,在 app
文件夹中名为 redux.js
的文件中(有意保持简单)
import { createStore } from 'tracked-redux';
export const actions = {
ARCHIVE_TASK: 'ARCHIVE_TASK',
PIN_TASK: 'PIN_TASK',
};
// The action creators bundle actions with the data required to execute them
export const archiveTask = id => ({ type: actions.ARCHIVE_TASK, id });
export const pinTask = id => ({ type: actions.PIN_TASK, id });
// A sample set of tasks
const defaultTasks = [
{ id: '1', title: 'Something', state: 'TASK_INBOX' },
{ id: '2', title: 'Something more', state: 'TASK_INBOX' },
{ id: '3', title: 'Something else', state: 'TASK_INBOX' },
{ id: '4', title: 'Something again', state: 'TASK_INBOX' },
];
// Store's initial state
const initialState = {
isError: false,
isLoading: false,
tasks: defaultTasks,
};
// All our reducers simply change the state of a single task.
function taskStateReducer(taskState) {
return (state, action) => {
return {
...state,
tasks: state.tasks.map(task =>
task.id === action.id ? { ...task, state: taskState } : task
),
};
};
}
// The reducer describes how the contents of the store change for each action
const reducers = (state, action) => {
switch (action.type) {
case actions.ARCHIVE_TASK:
return taskStateReducer('TASK_ARCHIVED')(state, action);
case actions.PIN_TASK:
return taskStateReducer('TASK_PINNED')(state, action);
default:
return state || initialState;
}
};
export const store = createStore(reducers);
使用路由
我们已经设置好了 store。现在我们可以在需要的对象上声明字段。
为此,我们将同时使用 route 和 controller。后者将包含我们之前创建的操作,以便我们可以轻松修改我们的 store。
在 app
目录中,创建一个名为 tasks
的新目录,并在其中添加一个名为 route.js
的新文件,内容如下
import Route from '@ember/routing/route';
import { store } from '../store';
export default class TasksRoute extends Route {
model() {
// returns the store tracked state
// whenever the state changes, these will be reflected in the template
return store
.getState()
.tasks.filter(t => t.state === 'TASK_INBOX' || t.state === 'TASK_PINNED');
}
}
接下来,我们需要 controller。在 tasks
文件夹中,创建另一个名为 controller.js
的文件,内容如下
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { store, pinTask, archiveTask } from '../store';
export default class TaskController extends Controller {
@action
pinTask(task) {
store.dispatch(pinTask(task));
}
@action
archiveTask(task) {
store.dispatch(archiveTask(task));
}
}
最后一个文件名为 template.hbs
,在其中我们将添加在 上一章 中创建的展示型 <TaskList>
组件
<TaskList
@tasks={{@model}}
@pinTask={{this.pinTask}}
@archiveTask={{this.archiveTask}}
/>
通过这样做,我们已经完成了我们想要做的事情,我们已经设法建立了一个数据持久层,并且通过采用一些最佳实践,我们还设法保持组件的解耦。
我们的实现相当基础,如果我们决定更新我们的应用程序,则需要额外的工作。在下一章中,我们将介绍屏幕组件,这将改进我们的小型应用程序中数据处理的方式。