TodoMVC-Cycle.js(WIP)
这次我们来写一个惯例的todomvc
当前状态:暂停--等待上游修复cyclic-router的bug
这次我们来写一个惯例的todomvc。
0x00 前期准备
当然基本的node.js
环境要求还是有的。没有就自己安装,推荐安装v8
以上的lts
版本。
其外还有git
和我个人偏好的yarn
。
通过模板项目创建
我们直接使用npx
启动create-cycle-app
,就不全局安装了,对于这一类工具,推荐使用npx
,因为每次使用前都去更新一下,还不如直接npx
自动使用最新版来得便利,当然如果你需要锁定版本,那还是全局安装的好。运行命令:
npx create-cycle-app todomvc-demo
添加其他依赖
因为create-cycle-app
使用的npm
安装包依赖的,我个人偏好yarn
,所以进去把package-lock.json
删掉,然后再添加todomvc
相关的包。
其外因为依赖中的husky
需要git
环境,所以如果你在用git
,那么还需要进去git init
一下,再运行yarn
或者npm
让husky
的git hook
起作用。运行命令:
cd todomvc-demo
rm package-lock.json
git init
yarn add todomvc-common todomvc-app-css @cycle/storage #这一步yarn会完成husky的配置
0x01 基础准备
首当其冲的是把.gitignore
改一下,避免添加IDE
相关的配置(看个人偏好)。
build/
.tmp/
.nyc_output/
node_modules/
coverage/
public/index.html
*.log
# JetBrains IDE configuration
.idea/
接着修改index.ejs
:
<!DOCTYPE html>
<html lang="en" data-framework="cycle">
<head>
<meta charset="utf-8">
<title>Cycle • TodoMVC</title>
</head>
<body>
<div id="app" class="todoapp"></div>
<footer class="info">
<p>Double-click to edit a todo</p>
</footer>
</body>
</html>
其中#app
是我们程序的挂载点,#app.todoapp
和footer.info
部分是todomvc
的css
。
接下来我们修改src/css/style.scc
:
@import "~todomvc-common/base.css";
@import "~todomvc-app-css/index.css";
html {
width: 100%;
height: 100%;
}
span,
button,
textarea {
margin: 0.5em;
}
0x03 创建根组件
创建目录结构
我个人习惯,components
目录是放跨页面共享的组件,routes
目录放对应路由下的组件和逻辑,但是这次我们就一个组件所以就不这么折腾了。
- 把
app.tsx
放到src
下,可以改名为app.ts
,因为我并不打算使用jsx
。 components
下添加TaskList
目录和Task
目录。- 在
TaskList
和Task
下分别添加index.ts
、interfaces.ts
、model.ts
、view.ts
、intent.ts
。
最终目录如下:
tree src
src
├── app.ts
├── components
│ ├── Task
│ │ ├── index.ts
│ │ ├── intent.ts
│ │ ├── interfaces.ts
│ │ ├── model.ts
│ │ └── view.ts
│ └── TaskList
│ ├── index.ts
│ ├── intent.ts
│ ├── interfaces.ts
│ ├── model.ts
│ └── view.ts
├── css
│ └── styles.scss
├── drivers
│ └── speech.ts
├── drivers.ts
├── index.ts
└── interfaces.ts
至此,文件结构就差不多了,剩下还有譬如util
这一类文件,需要的时候再添加。
修改周边文件
我们要用到loalStorage
和捕捉路由跳转,所以改一下drivers.ts
:
import { makeDOMDriver } from '@cycle/dom';
import { captureClicks, makeHistoryDriver } from '@cycle/history';
import storageDriver from '@cycle/storage';
import { withState } from '@cycle/state';
import { routerify } from 'cyclic-router';
import switchPath from 'switch-path';
import { Component } from './interfaces';
import speechDriver from './drivers/speech';
const driversFactories: any = {
DOM: () => makeDOMDriver('#app'),
history: () => captureClicks(makeHistoryDriver()),
speech: () => speechDriver,
storage: () => storageDriver
};
export function getDrivers(): any {
return Object.keys(driversFactories)
.map(k => ({ [k]: driversFactories[k]() }))
.reduce((a, c) => ({ ...a, ...c }), {});
}
export const driverNames = Object.keys(driversFactories)
.filter(name => name !== 'history')
.concat(['state', 'router']);
export function wrapMain(main: Component<any>): Component<any> {
return withState(routerify(main as any, switchPath as any)) as any;
}
其外还需要将默认路由指向TaskList
,所以再修改一下app.ts
:
import { Stream } from 'xstream';
import { extractSinks } from 'cyclejs-utils';
import isolate from '@cycle/isolate';
import { driverNames } from './drivers';
import { Component, Sinks, Sources } from './interfaces';
import { TaskList, State as TaskListState } from './components/TaskList';
export interface State {
taskList?: TaskListState;
}
export function App(sources: Sources<State>): Sinks<State> {
const match$ = sources.router.define({
'/': isolate(TaskList, 'taskList'),
});
const componentSinks$: Stream<Sinks<State>> = match$
.filter(({ path, value }: any) => path && typeof value === 'function')
.map(({ path, value }: { path: string; value: Component<any> }) => {
return value({
...sources,
router: sources.router.path(path)
});
});
return extractSinks(componentSinks$, driverNames);
}
目前还跑不起来,接下来开始修改应用的入口组件:TaskList
根组件
英文名为 RootComponent,意指一个组件树中根级节点,在react
中一般是一个Container
/Provider
类型组件,然而Cycle.js
中不存在这类组件,所以只是一个常规组件。另外,Cycle.js中对于react
的HOC
也有相关实现,亦即HOF
,因为Cycle.js
的一个组件就是一个纯函数,譬如isolate
、withState
和routerify
等就是HOF
。
现在我们来创建根组件,个人习惯先从view
入手:
// TODO