React 是最流行和简单的 JavaScript 库之一,用于构建用户界面 (UI),因为它允许您创建可重用的 UI 组件。
React 中的组件是独立的、可重用的代码片段,作为应用程序的构建块。React 函数式组件是 JavaScript 函数,它将表示层与业务逻辑分离开来。根据 React 文档,一个简单的函数式组件可以这样编写:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
React 函数式组件是无状态的。无状态组件被声明为没有状态并返回相同标记的函数(给定相同的 props)。状态在组件中使用 hooks 进行管理,hooks 在 React 16.8 中引入。它们实现了函数式组件的状态和生命周期管理。有几个内置的 hooks,您也可以创建自定义 hooks。
本文解释了如何使用函数式组件和状态管理在 React 中构建一个简单的待办事项应用。此应用的完整代码可在 GitHub 和 CodeSandbox 上找到。完成本教程后,该应用将如下所示:

(Jaivardhan Kumar, CC BY-SA 4.0)
先决条件
创建一个 React 应用
Create React App 是一个允许您开始构建 React 应用的环境。在本教程中,我使用了 TypeScript 模板来添加静态类型定义。TypeScript 是一种构建在 JavaScript 之上的开源语言
npx create-react-app todo-app-context-api --template typescript
yarn create react-app todo-app-context-api --template typescript
执行此命令后,您可以导航到该目录并运行该应用
cd todo-app-context-api
yarn start
您应该看到启动器应用和 React 徽标,这是由样板代码生成的。由于您正在构建自己的 React 应用,因此您将能够修改徽标和样式以满足您的需求。
构建待办事项应用
待办事项应用可以:
- 添加项目
- 列出项目
- 将项目标记为已完成
- 删除项目
- 根据状态(例如,已完成、全部、活动)筛选项目

(Jaivardhan Kumar, CC BY-SA 4.0)
Header 组件
创建一个名为 components 的目录,并添加一个名为 Header.tsx 的文件
mkdir components
cd components
vi Header.tsx
Header 是一个函数式组件,用于保存标题
const Header: React.FC = () => {
return (
<div className="header">
<h1>
Add TODO List!!
</h1>
</div>
)
}
AddTodo 组件
AddTodo 组件包含一个文本框和一个按钮。单击该按钮会将项目添加到列表中。
在 components 目录下创建一个名为 todo 的目录,并添加一个名为 AddTodo.tsx 的文件
mkdir todo
cd todo
vi AddTodo.tsx
AddTodo 是一个接受 props 的函数式组件。Props 允许单向传递数据,即仅从父组件到子组件
const AddTodo: React.FC<AddTodoProps> = ({ todoItem, updateTodoItem, addTaskToList }) => {
const submitHandler = (event: SyntheticEvent) => {
event.preventDefault();
addTaskToList();
}
return (
<form className="addTodoContainer" onSubmit={submitHandler}>
<div className="controlContainer">
<input className="controlSpacing" style={{flex: 1}} type="text" value={todoItem?.text ?? ''} onChange={(ev) => updateTodoItem(ev.target.value)} placeholder="Enter task todo ..." />
<input className="controlSpacing" style={{flex: 1}} type="submit" value="submit" />
</div>
<div>
<label>
<span style={{ color: '#ccc', padding: '20px' }}>{todoItem?.text}</span>
</label>
</div>
</form>
)
}
您已经创建了一个名为 AddTodo 的函数式 React 组件,它接受父函数提供的 props。这使得该组件可重用。需要传递的 props 是:
- todoItem: 一个空的待办事项状态
- updateToDoItem: 一个辅助函数,用于在用户键入时向父组件发送回调
- addTaskToList: 一个将项目添加到待办事项列表的函数
还有一些样式和 HTML 元素,例如 form、input 等。
TodoList 组件
要创建的下一个组件是 TodoList。它负责列出待办事项状态中的项目,并提供删除和标记项目为已完成的选项。
TodoList 将是一个函数式组件
const TodoList: React.FC = ({ listData, removeItem, toggleItemStatus }) => {
return listData.length > 0 ? (
<div className="todoListContainer">
{ listData.map((lData) => {
return (
<ul key={lData.id}>
<li>
<div className="listItemContainer">
<input type="checkbox" style={{ padding: '10px', margin: '5px' }} onChange={() => toggleItemStatus(lData.id)} checked={lData.completed}/>
<span className="listItems" style={{ textDecoration: lData.completed ? 'line-through' : 'none', flex: 2 }}>{lData.text}</span>
<button type="button" className="listItems" onClick={() => removeItem(lData.id)}>Delete</button>
</div>
</li>
</ul>
)
})}
</div>
) : (<span> No Todo list exist </span >)
}
TodoList 也是一个可重用的函数式 React 组件,它接受来自父函数的 props。需要传递的 props 是:
- listData: 待办事项列表,包含 ID、文本和已完成属性
- removeItem: 一个辅助函数,用于从待办事项列表中删除项目
- toggleItemStatus: 一个函数,用于在已完成和未完成之间切换任务状态
还有一些样式和 HTML 元素(例如列表、input 等)。
Footer 组件
Footer 将是一个函数式组件;在 components 目录下创建它,如下所示:
cd ..
const Footer: React.FC = ({item = 0, storage, filterTodoList}) => {
return (
<div className="footer">
<button type="button" style={{flex:1}} onClick={() => filterTodoList(ALL_FILTER)}>All Item</button>
<button type="button" style={{flex:1}} onClick={() => filterTodoList(ACTIVE_FILTER)}>Active</button>
<button type="button" style={{flex:1}} onClick={() => filterTodoList(COMPLETED_FILTER)}>Completed</button>
<span style={{color: '#cecece', flex:4, textAlign: 'center'}}>{item} Items | Make use of {storage} to store data</span>
</div>
);
}
它接受三个 props:
- item: 显示项目数量
- storage: 显示文本
- filterTodoList: 一个函数,用于根据状态(活动、已完成、所有项目)筛选任务
Todo 组件:使用 contextApi 和 useReducer 管理状态

(Jaivardhan Kumar, CC BY-SA 4.0)
Context 提供了一种在组件树中传递数据的方法,而无需在每个级别手动传递 props。ContextApi 和 useReducer 可用于管理状态,方法是在整个 React 组件树中共享状态,而无需将其作为 prop 传递给树中的每个组件。
现在您已经有了 AddTodo、TodoList 和 Footer 组件,您需要将它们连接起来。
使用以下内置 hooks 来管理组件的状态和生命周期:
- useState: 返回状态值和更新器函数以更新状态
- useEffect: 帮助管理函数式组件中的生命周期并执行副作用
- useContext: 接受一个 context 对象并返回当前 context 值
- useReducer: 与 useState 类似,它返回状态值和更新器函数,但当您有复杂的状态逻辑时(例如,多个子值或新状态取决于前一个状态时),它将代替 useState 使用
首先,使用 contextApi 和 useReducer hooks 管理状态。为了关注点分离,在 components 下添加一个名为 contextApiComponents 的新目录
mkdir contextApiComponents
cd contextApiComponents
创建 TodoContextApi.tsx
const defaultTodoItem: TodoItemProp = { id: Date.now(), text: '', completed: false };
const TodoContextApi: React.FC = () => {
const { state: { todoList }, dispatch } = React.useContext(TodoContext);
const [todoItem, setTodoItem] = React.useState(defaultTodoItem);
const [todoListData, setTodoListData] = React.useState(todoList);
React.useEffect(() => {
setTodoListData(todoList);
}, [todoList])
const updateTodoItem = (text: string) => {
setTodoItem({
id: Date.now(),
text,
completed: false
})
}
const addTaskToList = () => {
dispatch({
type: ADD_TODO_ACTION,
payload: todoItem
});
setTodoItem(defaultTodoItem);
}
const removeItem = (id: number) => {
dispatch({
type: REMOVE_TODO_ACTION,
payload: { id }
})
}
const toggleItemStatus = (id: number) => {
dispatch({
type: UPDATE_TODO_ACTION,
payload: { id }
})
}
const filterTodoList = (type: string) => {
const filteredList = FilterReducer(todoList, {type});
setTodoListData(filteredList)
}
return (
<>
<AddTodo todoItem={todoItem} updateTodoItem={updateTodoItem} addTaskToList={addTaskToList} />
<TodoList listData={todoListData} removeItem={removeItem} toggleItemStatus={toggleItemStatus} />
<Footer item={todoListData.length} storage="Context API" filterTodoList={filterTodoList} />
</>
)
}
此组件包括 AddTodo、TodoList 和 Footer 组件及其各自的辅助函数和回调函数。
为了管理状态,它使用 contextApi,它提供状态和 dispatch 方法,而 dispatch 方法又更新状态。它接受一个 context 对象。(接下来,您将创建 context 的 provider,称为 contextProvider)。
const { state: { todoList }, dispatch } = React.useContext(TodoContext);
TodoProvider
添加 TodoProvider,它创建 context 并使用 useReducer hook。useReducer hook 接受一个 reducer 函数以及初始值,并返回状态和更新器函数 (dispatch)。
- 创建 context 并导出它。导出它将允许任何子组件使用 hook useContext 获取当前状态
export const TodoContext = React.createContext({} as TodoContextProps);
- 创建 ContextProvider 并导出它
const TodoProvider : React.FC = (props) => { const [state, dispatch] = React.useReducer(TodoReducer, {todoList: []}); const value = {state, dispatch} return ( <TodoContext.Provider value={value}> {props.children} </TodoContext.Provider> ) }
- 如果您使用 provider(例如 TodoProvider)包装父组件(例如 TodoContextApi)或应用本身,则层次结构中的任何 React 组件都可以使用 useContext hook 直接访问 context 数据
<TodoProvider> <TodoContextApi /> </TodoProvider>
- 在 TodoContextApi 组件中,使用 useContext hook 访问当前 context 值
const { state: { todoList }, dispatch } = React.useContext(TodoContext)
TodoProvider.tsx
type TodoContextProps = {
state : {todoList: TodoItemProp[]};
dispatch: ({type, payload}: {type:string, payload: any}) => void;
}
export const TodoContext = React.createContext({} as TodoContextProps);
const TodoProvider : React.FC = (props) => {
const [state, dispatch] = React.useReducer(TodoReducer, {todoList: []});
const value = {state, dispatch}
return (
<TodoContext.Provider value={value}>
{props.children}
</TodoContext.Provider>
)
}
Reducers
Reducer 是一个没有副作用的纯函数。这意味着对于相同的输入,预期的输出将始终相同。这使得 reducer 更容易在隔离状态下进行测试并有助于管理状态。TodoReducer 和 FilterReducer 在组件 TodoProvider 和 TodoContextApi 中使用。
在 src 下创建一个名为 reducers 的目录,并在其中创建一个名为 TodoReducer.tsx 的文件
const TodoReducer = (state: StateProps = {todoList:[]}, action: ActionProps) => {
switch(action.type) {
case ADD_TODO_ACTION:
return { todoList: [...state.todoList, action.payload]}
case REMOVE_TODO_ACTION:
return { todoList: state.todoList.length ? state.todoList.filter((d) => d.id !== action.payload.id) : []};
case UPDATE_TODO_ACTION:
return { todoList: state.todoList.length ? state.todoList.map((d) => {
if(d.id === action.payload.id) d.completed = !d.completed;
return d;
}): []}
default:
return state;
}
}
创建一个 FilterReducer 以维护筛选器的状态
const FilterReducer =(state : TodoItemProp[] = [], action: ActionProps) => {
switch(action.type) {
case ALL_FILTER:
return state;
case ACTIVE_FILTER:
return state.filter((d) => !d.completed);
case COMPLETED_FILTER:
return state.filter((d) => d.completed);
default:
return state;
}
}
您已经创建了所有必需的组件。接下来,您将在 App 中添加 Header 和 TodoContextApi 组件,并将 TodoContextApi 与 TodoProvider 一起添加,以便所有子组件都可以访问 context。
function App() {
return (
<div className="App">
<Header />
<TodoProvider>
<TodoContextApi />
</TodoProvider>
</div>
);
}
确保 App 组件在 index.tsx 中的 ReactDom.render 内。ReactDom.render 接受两个参数:React Element 和 HTML 元素的 ID。React Element 在网页上呈现,id 指示哪个 HTML 元素将被 React Element 替换
ReactDOM.render(
<App />,
document.getElementById('root')
);
结论
您已经学习了如何使用 hooks 和状态管理在 React 中构建一个函数式应用。您将用它做什么呢?
评论已关闭。