以todolist为例,再不用redux的情况下,实现方式如下:
todo index:
import React, {useEffect, useState, useCallback} from 'react';
import {TodoAdd} from "./components/TodoAdd";
import {ToDos} from './components/todos'
export function TodoList() {
const [todos, setTodos] = useState([])
const addTodo = useCallback((toDo) => {
setTodos(todos => [...todos, toDo])
})
const removeTodo = useCallback((id) => {
setTodos(todos => todos.filter(todo => {
return todo.id !== id
}))
})
const ToggleTodo = useCallback((id) => {
setTodos(todos => todos.map(todo => {
return todo.id === id ? {
...todo,
complete: !todo.complete
} : todo
}))
})
useEffect(() => {
const todos = JSON.parse(localStorage.getItem('todo')) || []
setTodos(() => {
return todos
})
})
useEffect(() => {
localStorage.setItem('todo', JSON.stringify(todos))
}, [todos])
return (
<div>
<TodoAdd addTodo = {addTodo}></TodoAdd>
<ToDos todos={todos} removeTodo={removeTodo} ToggleTodo={ToggleTodo}></ToDos>
</div>
)
}
todoAdd :
import React, { useRef } from 'react';
let id = Date.now()
export function TodoAdd(props) {
const {addTodo} = props;
const inputRef = useRef()
const onSubmit = (e) => {
e.preventDefault()
const newText = inputRef.current.value.trim()
inputRef.current.value = ''
if(newText === ''){
return
}
addTodo({
id: ++id,
value: newText,
complete: false
})
}
return (
<div>
<form onSubmit={onSubmit}>
请输入<input type='text' ref={inputRef}/>
<input type='submit' value='add'/>
</form>
</div>
)
}
todoList
import React from 'react';
function TodoItem(props) {
const { todo, removeTodo, ToggleTodo } = props
return (
<div>
<input type='checkbox' onChange={() => {ToggleTodo(todo.id)}}/>
<span>{todo.value}</span>
<button onClick={() => {removeTodo(todo.id)}}>delete</button>
{todo.complete ? '选中' : '未选中'}
</div>
)
}
export function ToDos(props) {
const { todos, removeTodo, ToggleTodo } = props
return(
<div>
{
todos.map(todo => {
return <TodoItem
key={todo.id}
todo={todo}
removeTodo={removeTodo}
ToggleTodo={ToggleTodo}
/>
})
}
</div>
)
}
以上就是没有redux最初的todoList,下面做一些优化,把处理remove,add的过程都几种起来(集中到dispatch函数)用一个函数分情况处理。
todo index如下
import React, {useEffect, useState} from 'react';
import {TodoAdd} from "./components/TodoAdd";
import {ToDos} from './components/todos'
export function TodoList() {
const [todos, setTodos] = useState([])
const dispatch = (action) => {
const {type, data} = action
switch(type) {
case 'set' :
setTodos(data)
break;
case 'add':
setTodos(todos => [...todos, data])
break;
case 'remove':
setTodos(todos => todos.filter(todo => {
return todo.id !== data
}))
break
case 'toggle':
setTodos(todos => todos.map(todo => {
return todo.id === data ? {
...todo,
complete: !todo.complete
} : todo
}))
break
default:
break
}
}
useEffect(() => {
const todos = JSON.parse(localStorage.getItem('todo')) || []
const action = {
type: 'set',
data: todos
}
dispatch(action)
}, [])
useEffect(() => {
localStorage.setItem('todo', JSON.stringify(todos))
}, [todos])
return (
<div>
<TodoAdd dispatch = {dispatch}></TodoAdd>
<ToDos todos={todos} dispatch={dispatch}></ToDos>
</div>
)
}
todoAdd 如下
import React, { useRef } from 'react';
let id = Date.now()
export function TodoAdd(props) {
const {dispatch} = props;
const inputRef = useRef()
const onSubmit = (e) => {
e.preventDefault()
const newText = inputRef.current.value.trim()
inputRef.current.value = ''
if(newText === ''){
return
}
dispatch({type: 'add', data: {
id: ++id,
value: newText,
complete: false
}} )
}
return (
<div>
<form onSubmit={onSubmit}>
请输入<input type='text' ref={inputRef}/>
<input type='submit' value='add'/>
</form>
</div>
)
}
todos如下
import React from 'react';
function TodoItem(props) {
const { todo, dispatch } = props
return (
<div>
<input type='checkbox' onChange={() => {dispatch({type:'toggle', data: todo.id})}}/>
<span>{todo.value}</span>
<button onClick={() => {dispatch({type:'remove', data: todo.id})}}>delete</button>
{todo.complete ? '选中' : '未选中'}
</div>
)
}
export function ToDos(props) {
const { todos, dispatch} = props
return(
<div>
{
todos.map(todo => {
return <TodoItem
key={todo.id}
todo={todo}
dispatch={dispatch}
/>
})
}
</div>
)
}
以上的方法至少可以在一个地方(也就是dispatch函数内)统一处理所有的响应,但还是需要每个地方都手动创建action,下一步我们尝试将action也集中写到一个文件里,并写一个新的函数。用来集中为每一个action 调用dispatch方法(或者说用来dispatch每一个action),返回一个对象,包括所有dispatch所需action的新函数。然后解构这个对象,当做props传递给子组件。以下是实现代码。。。
todo index文件:
import React, {useEffect, useState} from 'react';
import {TodoAdd} from "./components/TodoAdd";
import {ToDos} from './components/todos'
import * as actions from './actionCreators'
function bindActionCreators(actionCreators, dispatch) {
const ret = {}
for(let key in actionCreators) {
ret[key] = function (...args) {
const actionCreator = actionCreators[key]
const action = actionCreator(...args)
dispatch(action)
}
}
return ret
}
export function TodoList() {
const [todos, setTodos] = useState([])
const dispatch = (action) => {
const {type, data} = action
switch(type) {
case 'set' :
setTodos(data)
break;
case 'add':
setTodos(todos => [...todos, data])
break;
case 'remove':
setTodos(todos => todos.filter(todo => {
return todo.id !== data
}))
break
case 'toggle':
setTodos(todos => todos.map(todo => {
return todo.id === data ? {
...todo,
complete: !todo.complete
} : todo
}))
break
default:
break
}
}
useEffect(() => {
const todos = JSON.parse(localStorage.getItem('todo')) || []
const action = {
type: 'set',
data: todos
}
dispatch(action)
}, [])
useEffect(() => {
localStorage.setItem('todo', JSON.stringify(todos))
}, [todos])
return (
<div>
<TodoAdd
{
...bindActionCreators({
addTodo: actions.createAdd
}, dispatch)
}
></TodoAdd>
<ToDos
{
...bindActionCreators({
removeTodo: actions.createRemove,
toggleTodo: actions.createToggle
}, dispatch)
}
todos={todos} ></ToDos>
</div>
)
}
addTodo 文件如下
import React, { useRef } from 'react';
let id = Date.now()
export function TodoAdd(props) {
const {addTodo} = props;
const inputRef = useRef()
const onSubmit = (e) => {
e.preventDefault()
const newText = inputRef.current.value.trim()
inputRef.current.value = ''
if(newText === ''){
return
}
addTodo({
id: ++id,
value: newText,
complete: false
} )
}
console.log(props)
return (
<div>
<form onSubmit={onSubmit}>
请输入<input type='text' ref={inputRef}/>
<input type='submit' value='add'/>
</form>
</div>
)
}
todolist 文件如下
import React from 'react';
function TodoItem(props) {
const { todo, removeTodo, toggleTodo } = props
return (
<div>
<input type='checkbox' onChange={() => {toggleTodo(todo.id)}}/>
<span>{todo.value}</span>
<button onClick={() => {removeTodo(todo.id)}}>delete</button>
{todo.complete ? '选中' : '未选中'}
</div>
)
}
export function ToDos(props) {
const { todos, removeTodo, toggleTodo} = props
return(
<div>
{
todos.map(todo => {
return <TodoItem
key={todo.id}
todo={todo}
removeTodo={removeTodo}
toggleTodo={toggleTodo}
/>
})
}
</div>
)
}
新增的actionCreator文件如下:
export function createSet(data) {
return {
type: 'set',
data: data
}
}
export function createAdd(data) {
return {
type: 'add',
data: data
}
}
export function createRemove(data) {
return {
type: 'remove',
data: data
}
}
export function createToggle(data) {
return {
type: 'toggle',
data: data
}
}
此时,所有的action都已被提了出来。所有的处理逻辑也都集中在dispatch中。但随着业务需求的增多。dispatch将越来越庞大,我们不妨将dispatch再分一下,dispatch函数内只保留存储数据的方法。至于需要存什么数据,交给新的函数reducer处理。
以下代码为添加了reducer,并改写dispatch的index文件
import React, {useEffect, useState} from 'react';
import {TodoAdd} from "./components/TodoAdd";
import {ToDos} from './components/todos'
import * as actions from './actionCreators'
function bindActionCreators(actionCreators, dispatch) {
const ret = {}
for(let key in actionCreators) {
ret[key] = function (...args) {
const actionCreator = actionCreators[key]
const action = actionCreator(...args)
dispatch(action)
}
}
return ret
}
function reducer(state, action) {
const {type, data} = action
const {todos, incrementCount} = state
switch (type) {
case 'set':
return{
...state,
todos: data
}
case 'add':
return {
...state,
todos: [...todos, data]
}
case 'remove':
return {
...state,
todos: todos.filter(todo => {
return todo.id !== data
})
}
case 'toggle':
return {
...state,
todos: todos.map(todo => {
return todo.id === data ? {
...todo,
complete: !todo.complete
} : todo
})
}
default:
break
}
}
export function TodoList() {
const [todos, setTodos] = useState([])
const [incrementCount, setIncrementCount] = useState(0)
const dispatch = (action) => {
const state = {
todos: todos,
incrementCount: incrementCount
}
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
const newState = reducer(state, action)
for(let key in newState) {
setters[key](newState[key])
}
}
useEffect(() => {
const todos = JSON.parse(localStorage.getItem('todo')) || []
const action = {
type: 'set',
data: todos
}
dispatch(action)
}, [])
useEffect(() => {
localStorage.setItem('todo', JSON.stringify(todos))
}, [todos])
return (
<div>
<TodoAdd
{
...bindActionCreators({
addTodo: actions.createAdd
}, dispatch)
}
></TodoAdd>
<ToDos
{
...bindActionCreators({
removeTodo: actions.createRemove,
toggleTodo: actions.createToggle
}, dispatch)
}
todos={todos} ></ToDos>
</div>
)
}
此时,dispatch函数就包含了两个步骤,第一是调用reducer函数得到返回的数据,第二是调用api保存数据、虽然单独提出了reducer用来解析处理数据,但还是没解决随着业务增多。从而导致reducer函数越来越大的局面。因此,我们有必要根据业务逻辑,将reducer函数分解,并通过工具函数合并reducer。我们在此新增加一个文件,reducer.js
reducer文件如下:
const reducers = {
todos(state, action) {
const {type, data} = action
const {todos} = state
debugger
switch (type) {
case 'set':
return data
case 'add':
return [...todos, data]
case 'remove':
return todos.filter(todo => {
return todo.id !== data
})
case 'toggle':
return todos.map(todo => {
return todo.id === data ? {
...todo,
complete: !todo.complete
} : todo
})
default:
break
}
},
incrementCount(state, action) {
const {type} = action
const {incrementCount} = state
switch(type) {
case 'set':
case 'add':
return incrementCount + 1
default:
//
break;
}
return incrementCount
}
}
const combineReducers = (reducers) => {
return function reducer(state, action) { // state 包含所有的数据(todos, incrementCount)
const changed = {}
for(let key in reducers) {
changed[key] = reducers[key](state, action) // {todos: [], incrementCount: n}
}
return {
...state,
...changed
}
}
}
export default combineReducers(reducers)
此时的index函数就变得不是那么臃肿了:
import React, {useEffect, useState} from 'react';
import {TodoAdd} from "./components/TodoAdd";
import {ToDos} from './components/todos'
import reducer from "./reducer";
import * as actions from './actionCreators'
function bindActionCreators(actionCreators, dispatch) {
const ret = {}
for(let key in actionCreators) {
ret[key] = function (...args) {
const actionCreator = actionCreators[key]
const action = actionCreator(...args)
dispatch(action)
}
}
return ret
}
export function TodoList() {
const [todos, setTodos] = useState([])
const [incrementCount, setIncrementCount] = useState(0)
const dispatch = (action) => {
const state = {
todos: todos,
incrementCount: incrementCount
}
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
const newState = reducer(state, action)
for(let key in newState) {
setters[key](newState[key])
}
}
useEffect(() => {
const todos = JSON.parse(localStorage.getItem('todo')) || []
const action = {
type: 'set',
data: todos
}
dispatch(action)
}, [])
useEffect(() => {
localStorage.setItem('todo', JSON.stringify(todos))
}, [todos])
return (
<div>
<TodoAdd
{
...bindActionCreators({
addTodo: actions.createAdd
}, dispatch)
}
incrementCount={incrementCount}
></TodoAdd>
<ToDos
{
...bindActionCreators({
removeTodo: actions.createRemove,
toggleTodo: actions.createToggle
}, dispatch)
}
todos={todos} ></ToDos>
</div>
)
}