示例: Todo 列表
这是我们在基础教程里开发的迷你型的任务管理应用的完整源码。
入口文件
index.js
import React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import App from "./containers/App";
import todoApp from "./reducers";
let store = createStore(todoApp);
let rootElement = document.getElementById("root");
React.render(
// 为了解决 React 0.13 的问题,
// 一定要把 child 用函数包起来。
<Provider store={store}>
{() => <App />}
</Provider>,
rootElement
);
Action 创建函数和常量
actions.js
/*
* action types
*/
export const ADD_TODO = "ADD_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
export const SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER"
/*
* 其它的常量
*/
export const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL",
SHOW_COMPLETED: "SHOW_COMPLETED",
SHOW_ACTIVE: "SHOW_ACTIVE"
};
/*
* action 创建函数
*/
export function addTodo(text) {
return { type: ADD_TODO, text };
}
export function completeTodo(index) {
return { type: COMPLETE_TODO, index };
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter };
}
Reducers
reducers.js
import { combineReducers } from "redux";
import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from "./actions";
const { SHOW_ALL } = VisibilityFilters;
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, {
text: action.text,
completed: false
}];
case COMPLETE_TODO:
return [
...state.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
];
default:
return state;
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
});
export default todoApp;
智能组件
containers/App.js
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from "../actions";
import AddTodo from "../components/AddTodo";
import TodoList from "../components/TodoList";
import Footer from "../components/Footer";
class App extends Component {
render() {
// Injected by connect() call:
const { dispatch, visibleTodos, visibilityFilter } = this.props;
return (
<div>
<AddTodo
onAddClick={text =>
dispatch(addTodo(text))
} />
<TodoList
todos={this.props.visibleTodos}
onTodoClick={index =>
dispatch(completeTodo(index))
} />
<Footer
filter={visibilityFilter}
onFilterChange={nextFilter =>
dispatch(setVisibilityFilter(nextFilter))
} />
</div>
);
}
}
App.propTypes = {
visibleTodos: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired
})),
visibilityFilter: PropTypes.oneOf([
"SHOW_ALL",
"SHOW_COMPLETED",
"SHOW_ACTIVE"
]).isRequired
};
function selectTodos(todos, filter) {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos;
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(todo => todo.completed);
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(todo => !todo.completed);
}
}
// Which props do we want to inject, given the global state?
// Note: use https://github.com/faassen/reselect for better performance.
function select(state) {
return {
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
visibilityFilter: state.visibilityFilter
};
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(App);
笨拙组件
components/AddTodo.js
import React, { findDOMNode, Component, PropTypes } from "react";
export default class AddTodo extends Component {
render() {
return (
<div>
<input type="text" ref="input" />
<button onClick={(e) => this.handleClick(e)}>
Add
</button>
</div>
);
}
handleClick(e) {
const node = findDOMNode(this.refs.input);
const text = node.value.trim();
this.props.onAddClick(text);
node.value = "";
}
}
AddTodo.propTypes = {
onAddClick: PropTypes.func.isRequired
};
components/Footer.js
import React, { Component, PropTypes } from "react";
export default class Footer extends Component {
renderFilter(filter, name) {
if (filter === this.props.filter) {
return name;
}
return (
<a href="#" onClick={e => {
e.preventDefault();
this.props.onFilterChange(filter);
}}>
{name}
</a>
);
}
render() {
return (
<p>
Show:
{" "}
{this.renderFilter("SHOW_ALL", "All")}
{", "}
{this.renderFilter("SHOW_COMPLETED", "Completed")}
{", "}
{this.renderFilter("SHOW_ACTIVE", "Active")}
.
</p>
);
}
}
Footer.propTypes = {
onFilterChange: PropTypes.func.isRequired,
filter: PropTypes.oneOf([
"SHOW_ALL",
"SHOW_COMPLETED",
"SHOW_ACTIVE"
]).isRequired
};
components/Todo.js
import React, { Component, PropTypes } from "react";
export default class Todo extends Component {
render() {
return (
<li
onClick={this.props.onClick}
style={{
textDecoration: this.props.completed ? "line-through" : "none",
cursor: this.props.completed ? "default" : "pointer"
}}>
{this.props.text}
</li>
);
}
}
Todo.propTypes = {
onClick: PropTypes.func.isRequired,
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired
};
components/TodoList.js
import React, { Component, PropTypes } from "react";
import Todo from "./Todo";
export default class TodoList extends Component {
render() {
return (
<ul>
{this.props.todos.map((todo, index) =>
<Todo {...todo}
key={index}
onClick={() => this.props.onTodoClick(index)} />
)}
</ul>
);
}
}
TodoList.propTypes = {
onTodoClick: PropTypes.func.isRequired,
todos: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired
}).isRequired).isRequired
};