├── .gitignore ├── README.md ├── component.json ├── fis-conf.js ├── index.html ├── modules ├── actions │ └── todos.ts ├── components │ ├── Footer.tsx │ ├── Header.tsx │ ├── MainSection.tsx │ ├── TodoItem.tsx │ └── TodoTextInput.tsx ├── constants │ ├── ActionTypes.ts │ └── TodoFilters.ts ├── containers │ └── App.tsx ├── index.tsx ├── reducers │ ├── index.ts │ └── todos.ts └── store │ └── configureStore.ts ├── package.json └── static ├── index.css └── mod.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | output 4 | 5 | /components 6 | 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FIS3 React + Redux 的 todo demo. 2 | 3 | ## 安装 fis3 4 | 5 | `npm install fis3 -g` 6 | 7 | **注意 fis3 版本请用 3.2.9 或者以上,否则需要把 .ts 文件后缀假如文本类型列表中** 8 | 9 | ## 初始化 10 | 11 | ```bash 12 | mkdir demo 13 | cd demo 14 | fis3 init react-redux-todo 15 | ``` 16 | 17 | ## 运行 & 预览 18 | 19 | ```bash 20 | fis3 release 21 | fis3 server start 22 | ``` 23 | 24 | 或者 25 | 26 | ``` 27 | npm start 28 | ``` 29 | 30 | ## 产出产品代码 31 | 32 | 只启用了 js 压缩和 合并。 33 | 34 | ``` 35 | fis3 release production -d ./output 36 | ``` 37 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "react@^0.14.2", 4 | "react-dom@^0.14.2", 5 | "redux@^3.0.4", 6 | "react-redux@^4.0.0", 7 | "classnames" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /fis-conf.js: -------------------------------------------------------------------------------- 1 | fis.set('project.files', '/index.html'); // 按需编译。 2 | 3 | // 采用 commonjs 模块化方案。 4 | fis.hook('commonjs', { 5 | baseUrl: './modules', 6 | extList: ['.js', '.ts', '.tsx'] 7 | }); 8 | 9 | fis.match('*.{ts,tsx}', { 10 | parser: fis.plugin('typescript'), 11 | rExt: '.js' 12 | }); 13 | 14 | // 设置成是模块化 js 15 | fis.match('/{components,modules}/**.{js,ts,tsx}', { 16 | isMod: true 17 | }); 18 | 19 | fis.match('::package', { 20 | // 本项目为纯前段项目,所以用 loader 编译器加载, 21 | // 如果用后端运行时框架,请不要使用。 22 | postpackager: fis.plugin('loader', { 23 | useInlineMap: true 24 | }) 25 | }); 26 | 27 | 28 | // 请用 fis3 release production 来启用。 29 | fis.media('production') 30 | 31 | // 对 js 做 uglify 压缩。 32 | .match('*.{js,ts,tsx}', { 33 | optimizer: fis.plugin('uglify-js') 34 | }) 35 | 36 | .match('::package', { 37 | 38 | // 更多用法请参考: https://github.com/fex-team/fis3-packager-deps-pack 39 | packager: fis.plugin('deps-pack', { 40 | 'pkg/index.js': /*当有多条时,请用数组*/[ 41 | 'modules/index.tsx', 42 | 'modules/index.tsx:deps', // 以及其所有依赖 43 | ] 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redux TodoMVC example 5 | 6 | 7 | 8 | 9 |
10 |
11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /modules/actions/todos.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | 3 | export function addTodo(text) { 4 | return { type: types.ADD_TODO, text } 5 | } 6 | 7 | export function deleteTodo(id) { 8 | return { type: types.DELETE_TODO, id } 9 | } 10 | 11 | export function editTodo(id, text) { 12 | return { type: types.EDIT_TODO, id, text } 13 | } 14 | 15 | export function completeTodo(id) { 16 | return { type: types.COMPLETE_TODO, id } 17 | } 18 | 19 | export function completeAll() { 20 | return { type: types.COMPLETE_ALL } 21 | } 22 | 23 | export function clearCompleted() { 24 | return { type: types.CLEAR_COMPLETED } 25 | } 26 | -------------------------------------------------------------------------------- /modules/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import classnames from 'classnames' 3 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' 4 | 5 | const FILTER_TITLES = { 6 | [SHOW_ALL]: 'All', 7 | [SHOW_ACTIVE]: 'Active', 8 | [SHOW_COMPLETED]: 'Completed' 9 | } 10 | 11 | class Footer extends Component { 12 | renderTodoCount() { 13 | const { activeCount } = this.props 14 | const itemWord = activeCount === 1 ? 'item' : 'items' 15 | 16 | return ( 17 | 18 | {activeCount || 'No'} {itemWord} left 19 | 20 | ) 21 | } 22 | 23 | renderFilterLink(filter) { 24 | const title = FILTER_TITLES[filter] 25 | const { filter: selectedFilter, onShow } = this.props 26 | 27 | return ( 28 | onShow(filter)}> 31 | {title} 32 | 33 | ) 34 | } 35 | 36 | renderClearButton() { 37 | const { completedCount, onClearCompleted } = this.props 38 | if (completedCount > 0) { 39 | return ( 40 | 44 | ) 45 | } 46 | } 47 | 48 | render() { 49 | return ( 50 | 61 | ) 62 | } 63 | } 64 | 65 | Footer.propTypes = { 66 | completedCount: PropTypes.number.isRequired, 67 | activeCount: PropTypes.number.isRequired, 68 | filter: PropTypes.string.isRequired, 69 | onClearCompleted: PropTypes.func.isRequired, 70 | onShow: PropTypes.func.isRequired 71 | } 72 | 73 | export default Footer 74 | -------------------------------------------------------------------------------- /modules/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import TodoTextInput from './TodoTextInput' 3 | 4 | class Header extends Component { 5 | handleSave(text) { 6 | if (text.length !== 0) { 7 | this.props.addTodo(text) 8 | } 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

todos

15 | 18 |
19 | ) 20 | } 21 | } 22 | 23 | Header.propTypes = { 24 | addTodo: PropTypes.func.isRequired 25 | } 26 | 27 | export default Header 28 | -------------------------------------------------------------------------------- /modules/components/MainSection.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import TodoItem from './TodoItem' 3 | import Footer from './Footer' 4 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' 5 | 6 | const TODO_FILTERS = { 7 | [SHOW_ALL]: () => true, 8 | [SHOW_ACTIVE]: todo => !todo.completed, 9 | [SHOW_COMPLETED]: todo => todo.completed 10 | } 11 | 12 | class MainSection extends Component { 13 | constructor(props, context) { 14 | super(props, context) 15 | this.state = { filter: SHOW_ALL } 16 | } 17 | 18 | handleClearCompleted() { 19 | const atLeastOneCompleted = this.props.todos.some(todo => todo.completed) 20 | if (atLeastOneCompleted) { 21 | this.props.actions.clearCompleted() 22 | } 23 | } 24 | 25 | handleShow(filter) { 26 | this.setState({ filter }) 27 | } 28 | 29 | renderToggleAll(completedCount) { 30 | const { todos, actions } = this.props 31 | if (todos.length > 0) { 32 | return ( 33 | 37 | ) 38 | } 39 | } 40 | 41 | renderFooter(completedCount) { 42 | const { todos } = this.props 43 | const { filter } = this.state 44 | const activeCount = todos.length - completedCount 45 | 46 | if (todos.length) { 47 | return ( 48 |