react hooks版本
header组件与input组件的编写
openInput事件:Header子组件通知父组件App控制AddInput子组件
Header.js
import React from 'react';
import './index.scss';
function Header (props) {
const { openInput } = props;
return (
<div className="header">
<h1>事件待办</h1>
<span className="icon" onClick={openInput}>
+
</span>
</div>
);
}
export default Header;
App.js
import React, { useState } from 'react';
import './App.scss';
import MyHeader from './components/Header';
functoin App() {
const [ isShowInput, setIsShowInput ] = useState(false)
return (
<div className="App">
<MyHeader openInput={() => setIsShowInput(!isShowInput)} />
</div>
);
}
export default App;
AddInput.js
import React from 'react';
import './index.scss';
function AddInput (props) {
const { isShow } = props,
return (
<>
{isShow && (
<div className="input-wrapper">
<input type="text" placeholder="请输入待办事件" />
<button className="btn btn-primary">
增加
</button>
</div>
)}
</>
);
}
export default AddInput;
输入增加待办事项
usecallback
AddInput子组件将输入的数据传递给父组件App,App将数据存起来
import React, { useCallback, useRef } from 'react';
import './index.scss';
function AddInput (props) {
const { isShow, addItem } = props,
inputRef = useRef();
const submitValue = useCallback(
() => {
const inputValue = inputRef.current.value.trim();
if (inputValue.length === 0) {
return;
}
addItem(inputValue);
inputRef.current.value = '';
},
[addItem]
);
return (
<>
{isShow && (
<div className="input-wrapper">
<input type="text" ref={inputRef} placeholder="请输入待办事件" />
<button className="btn btn-primary" onClick={submitValue}>
增加
</button>
</div>
)}
</>
);
}
App.js
import React, { useState, useEffect, useCallback } from 'react';
import './App.scss';
import MyHeader from './components/Header';
import AddInput from './components/AddInput';
import TodoItem from './components/TodoItem';
function App() {
const [ isShowInput, setIsShowInput ] = useState(false),
[ todoList, setTodoList ] = useState([])
const addItem = useCallback(
(value) => {
const dataItem = {
id: new Date().getTime(),
content: value,
completed: false,
};
setTodoList((todoList) => [...todoList, dataItem]);
setIsShowInput(false);
},
[]
);
return (
<div className="App">
<MyHeader openInput={() => setIsShowInput(!isShowInput)} />
<AddInput isShow={isShowInput} addItem={(value) => addItem(value)} />
)}
</div>
);
}
export default App;
遍历数据循环渲染ToDo列表
App将数据其传递给TODOItem子组件进行显示
App.js
import React, { useState, useEffect, useCallback } from 'react';
import './App.scss';
import MyHeader from './components/Header';
import AddInput from './components/AddInput';
import TodoItem from './components/TodoItem';
function App() {
const [ isShowInput, setIsShowInput ] = useState(false),
[ todoList, setTodoList ] = useState([])
const addItem = useCallback(
(value) => {
const dataItem = {
id: new Date().getTime(),
content: value,
completed: false,
};
setTodoList((todoList) => [...todoList, dataItem]);
setIsShowInput(false);
},
[]
);
return (
<div className="App">
<MyHeader openInput={() => setIsShowInput(!isShowInput)} />
<AddInput isShow={isShowInput} addItem={(value) => addItem(value)} />
<ul className="todo-list">
{todoList.map((item, index) => {
return (
<TodoItem
dataItem={item}
key={index}
/>
);
})}
</ul>
)}
</div>
);
}
export default App;
TodoItem,js
import React from 'react';
import './index.scss';
function TodoItem (props) {
const {
dataItem,
} = props;
return (
<li className="todo-item">
<div className="check-box">
<input
type="checkbox"
checked={dataItem.completed}
/>
</div>
<span
className="content"
style={{ textDecoration: dataItem.completed ? "line-through" : "none" }}
>
{dataItem.content}
</span>
<div className="btn-group">
<button
className="btn btn-primary"
>
查看
</button>
<button
className="btn btn-warning"
>
编辑
</button>
<button
className="btn btn-danger"
>
删除
</button>
</div>
</li>
);
}
export default TodoItem;
数据存储到localStorage
useeffct用法
App.js
useEffect(() => {
const todoData = JSON.parse(localStorage.getItem("todoData") || '[]');
setTodoList(todoData);
}, []);
useEffect(() => {
localStorage.setItem("todoData", JSON.stringify(todoList));
}, [todoList]);
模态框组件与插槽
基础容器Model的插槽中添加一定的内容,构成了CheckModel和EditModel组件。
Model.js
import React from 'react';
import '../../assets/css/common.scss';
function Modal (props) {
const { isShowModal, modalTitle, children } = props;
return (
<>
{isShowModal ? (
<div className="modal">
<div className="inner">
<div className="m-header">{modalTitle}</div>
<div className="content-wrapper">{children}</div>
</div>
</div>
) : (
""
)}
</>
);
}
export default Modal;
CheckModal.js
import React from 'react';
import './index.scss';
import Modal from '../';
import { formatDateTime } from '../../../libs/utils';
function CheckModal (props) {
const { isShowCheckModal, data, closeModal } = props;
return (
<Modal isShowModal={isShowCheckModal} modalTitle="查看事件">
<p className="topic">时间:{formatDateTime(data.id)}</p>
<p className="topic">内容:{ data.content }</p>
<p className="topic">状态:{ data.completed ? '已完成' : '未完成' }</p>
<button
className="btn btn-primary comfirm-btn"
onClick={ closeModal }
>确定</button>
</Modal>
);
}
export default CheckModal;
EditModal.js
import React, { useRef, useCallback } from 'react';
import './index.scss';
import Modal from '../';
import { formatDateTime } from '../../../libs/utils';
function EditModal (props) {
const { isShowEditModal, data, submitEdit } = props,
inputRef = useRef(),
checkRef = useRef();
const formatNewData = useCallback(() => {
const inputLen = inputRef.current.value.trim().length;
if (inputLen === 0) {
inputRef.current.value = data.content;
return;
}
const newData = {
id: new Date().getTime(),
content: inputRef.current.value,
completed: checkRef.current.checked,
};
submitEdit(newData, data.id);
}, [submitEdit, data]);
return (
<Modal isShowModal={isShowEditModal} modalTitle="编辑事件">
<p className="topic">时间:{formatDateTime(data.id)}</p>
<p className="topic">
<textarea
ref={inputRef}
defaultValue={data.content}
className="text-area"
></textarea>
</p>
<p className="topic">
状态:
<input
type="checkbox"
defaultChecked={data.completed ? true : false}
ref={checkRef}
/>
</p>
<button className="btn btn-primary comfirm-btn" onClick={formatNewData}>
确定
</button>
</Modal>
);
}
export default EditModal;
App.js
import React, { useState, useEffect, useCallback } from 'react';
import './App.scss';
import MyHeader from './components/Header';
import AddInput from './components/AddInput';
import NoDataTip from './components/NoDataTip';
import TodoItem from './components/TodoItem';
import CheckModal from './components/Modal/CheckModal';
import EditModal from './components/Modal/EditModal';
function App() {
const [ isShowInput, setIsShowInput ] = useState(false),
[ isShowCheckModal, setIsShowCheckModal ] = useState(false),
[ isShowEditModal, setIsShowEditModal ] = useState(false),
[ todoList, setTodoList ] = useState([]),
[ currentData, setCurrentData ] = useState({});
useEffect(() => {
const todoData = JSON.parse(localStorage.getItem("todoData") || '[]');
setTodoList(todoData);
}, []);
useEffect(() => {
localStorage.setItem("todoData", JSON.stringify(todoList));
}, [todoList]);
const addItem = useCallback(
(value) => {
const dataItem = {
id: new Date().getTime(),
content: value,
completed: false,
};
setTodoList((todoList) => [...todoList, dataItem]);
setIsShowInput(false);
},
[]
);
const removeItem = useCallback((id) => {
setTodoList((todoList) => todoList.filter((item) => item.id !== id));
}, []);
const completeItem = useCallback((id) => {
setTodoList((todoList) => todoList.map((item) => {
if (item.id === id) {
item.completed = !item.completed;
}
return item;
}));
}, [])
const openCheckModal = useCallback(
(id) => {
setCurrentData(() => todoList.filter((item) => item.id === id)[0]);
setIsShowCheckModal(true);
},
[todoList]
);
const openEditModal = useCallback(
(id) => {
setCurrentData(() => todoList.filter((item) => item.id === id)[0]);
setIsShowEditModal(true);
},
[todoList]
);
const submitEdit = useCallback(
(newData, id) => {
setTodoList((todoList) =>
todoList.map((item) => {
if (item.id === id) {
item = newData;
}
return item;
})
);
setIsShowEditModal(false);
},
[]
);
return (
<div className="App">
<CheckModal
isShowCheckModal={isShowCheckModal}
data={currentData}
closeModal={() => setIsShowCheckModal(false)}
/>
<EditModal
isShowEditModal={isShowEditModal}
data={currentData}
submitEdit={submitEdit}
/>
<MyHeader openInput={() => setIsShowInput(!isShowInput)} />
<AddInput isShow={isShowInput} addItem={(value) => addItem(value)} />
{!todoList || todoList.length === 0 ? (
<NoDataTip />
) : (
<ul className="todo-list">
{todoList.map((item, index) => {
return (
<TodoItem
dataItem={item}
key={index}
removeItem={removeItem}
openCheckModal={openCheckModal}
completeItem={completeItem}
openEditModal={openEditModal}
/>
);
})}
</ul>
)}
</div>
);
}
export default App;
TodoItem.js
import React from 'react';
import './index.scss';
function TodoItem (props) {
const {
removeItem,
completeItem,
openCheckModal,
openEditModal,
dataItem,
} = props;
return (
<li className="todo-item">
<div className="check-box">
<input
type="checkbox"
checked={dataItem.completed}
onChange={() => completeItem(dataItem.id)}
/>
</div>
<span
className="content"
style={{ textDecoration: dataItem.completed ? "line-through" : "none" }}
>
{dataItem.content}
</span>
<div className="btn-group">
<button
className="btn btn-primary"
onClick={() => openCheckModal(dataItem.id)}
>
查看
</button>
<button
className="btn btn-warning"
onClick={() => openEditModal(dataItem.id)}
>
编辑
</button>
<button
className="btn btn-danger"
onClick={() => removeItem(dataItem.id)}
>
删除
</button>
</div>
</li>
);
}
export default TodoItem;
日期格式化
utils.js
function _addZero(value) {
return value < 10 ? "0" + value : value;
}
function formatDateTime (timeStamp) {
const date = new Date(timeStamp);
const y = date.getFullYear(),
m = _addZero(date.getMonth() + 1),
d = _addZero(date.getDate()),
h = _addZero(date.getHours()),
i = _addZero(date.getMinutes()),
s = _addZero(date.getSeconds());
return `${y}年${m}月${d}日 ${h}:${i}:${s}`;
}
export {
formatDateTime
}
Redux版本
- 数据存在Store
- 想要更新数据使用Action,Action里面的type为数据类型,data为操作数据时候的所需的附加参数
- 通过Action实现状态数据的更新,需要使用reducer,reducer需要两个参数,一个是数据的前状态,第二个是Action,这就实现了数据的更新
- 数据更新显示在识图
TodoHeader + TodoMain + TodoItem + TodoFooter
常(constant) 爱(action) 瑞(redux) 组(component)
跑通Redux
App.js
import React from 'react'
import TodoHeader from './components/TodoHeader'
import TodoMain from './components/TodoMain'
import TodoFooter from './components/TodoFooter'
export default function App() {
return (
<section className='todoapp'>
<TodoHeader />
<TodoMain />
<TodoFooter />
</section>
)
}
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import './styles/base.css'
import './styles/index.css'
import App from './App'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#root')
)
TodoHeader.js
import React from 'react'
export default function TodoHeader() {
return (
<header className='header'>
<h1>todos</h1>
<input
className='new-todo'
placeholder='What needs to be done?'
autoFocus
/>
</header>
)
}
TodoMain.js
import React from 'react'
export default function TodoMain() {
return (
<section className='main'>
<input id='toggle-all' className='toggle-all' type='checkbox' />
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
<li className='completed'>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked
onChange={() => {}}
/>
<label>Taste JavaScript</label>
<button className='destroy'></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
<li>
<div className='view'>
<input className='toggle' type='checkbox' />
<label>Buy a unicorn</label>
<button className='destroy'></button>
</div>
<input className='edit' value='Rule the web' onChange={() => {}} />
</li>
</ul>
</section>
)
}
TodoFooter
import React from 'react'
export default function TodoFooter() {
return (
<footer className='footer'>
<span className='todo-count'>
<strong>0</strong> item left
</span>
<ul className='filters'>
<li>
<a className='selected' href='#/'>
All
</a>
</li>
<li>
<a href='#/active'>Active</a>
</li>
<li>
<a href='#/completed'>Completed</a>
</li>
</ul>
<button className='clear-completed'>Clear completed</button>
</footer>
)
}
列表渲染
src / store / reducers / todo.js
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
return state
}
src/store/reducers/index.js
// 组合 reducers
import { combineReducers } from 'redux'
import todo from './todo'
export default combineReducers({ todo })
src/store/index.js
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducers'
export default createStore(rootReducer, composeWithDevTools())
TodoMain.js
import { useSelector } from 'react-redux'
import classNames from 'classnames'
export default function TodoMain() {
const lists = useSelector((state) => state.todo)
return (
<section className='main'>
<input id='toggle-all' className='toggle-all' type='checkbox' />
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<li
key={item.id}
className={classNames({
completed: item.done,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => {}}
/>
<label>{item.name}</label>
<button className='destroy'></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
))}
</ul>
</section>
)
}
删除
src/store/constants/todo.js
export const TODO_DEL = 'TODO_DEL'
src/store/constants/index.js
export { TODO_DEL } from './todo'
src/store/actions/todo.js
import { TODO_DEL } from '../constants'
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})
src/store/actions/index.js
export { todoDel } from './todo'
src/store/reducers/todo.js
import { TODO_DEL } from '../constants'
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id)
default:
return state
}
}
src/store/reducers/index.js
// 组合 reducers
import { combineReducers } from 'redux'
import todo from './todo'
export default combineReducers({ todo })
TodoMain
import { useSelector, useDispatch } from 'react-redux'
import classNames from 'classnames'
import { todoDel } from '../../store/actions/todo'
// 常量 => actionCreator => action(常量) => Reducer => 组件
// 常爱瑞组:优秀!
export default function TodoMain() {
const dispatch = useDispatch()
const lists = useSelector((state) => state.todo)
const handleDel = (id) => dispatch(todoDel(id))
return (
<section className='main'>
<input id='toggle-all' className='toggle-all' type='checkbox' />
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<li
key={item.id}
className={classNames({
completed: item.done,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => {}}
/>
<label>{item.name}</label>
<button
className='destroy'
onClick={() => handleDel(item.id)}
></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
))}
</ul>
</section>
)
}
状态切换
src/store/constants/todo.js
export const TODO_DEL = 'TODO_DEL'
export const TODO_CHANGE_DONE = 'TODO_CHANGE_DONE'
src/store/constants/index.js
export { TODO_DEL, TODO_CHANGE_DONE } from './todo'
src/store/actions/todo.js
import { TODO_CHANGE_DONE, TODO_DEL } from '../constants'
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})
export const todoChangeDone = (id) => ({
type: TODO_CHANGE_DONE,
id,
})
src/store/actions/index.js
export { todoDel, todoChangeDone } from './todo'
src/store/reducers/todo.js
import { TODO_CHANGE_DONE, TODO_DEL } from '../constants'
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id)
case TODO_CHANGE_DONE:
return state.map((item) =>
item.id === action.id
? {
...item,
done: !item.done,
}
: item
)
default:
return state
}
}
TodoMain
import { useSelector, useDispatch } from 'react-redux'
import classNames from 'classnames'
import { todoChangeDone, todoDel } from '../../store/actions/todo'
// 常量 => actionCreator => action(常量) => Reducer => 组件
// 常爱瑞组:优秀!
export default function TodoMain() {
const dispatch = useDispatch()
const lists = useSelector((state) => state.todo)
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
return (
<section className='main'>
<input id='toggle-all' className='toggle-all' type='checkbox' />
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<li
key={item.id}
className={classNames({
completed: item.done,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label>{item.name}</label>
<button
className='destroy'
onClick={() => handleDel(item.id)}
></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
))}
</ul>
</section>
)
}
添加
src/store/constants/todo.js
export const TODO_DEL = 'TODO_DEL'
export const TODO_CHANGE_DONE = 'TODO_CHANGE_DONE'
export const TODO_ADD = 'TODO_ADD'
src/store/constants/index.js
export { TODO_DEL, TODO_CHANGE_DONE, TODO_ADD } from './todo'
src/store/actions/todo.js
import { TODO_ADD, TODO_CHANGE_DONE, TODO_DEL } from '../constants'
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})
export const todoChangeDone = (id) => ({
type: TODO_CHANGE_DONE,
id,
})
export const todoAdd = (name) => ({
type: TODO_ADD,
id: Date.now(),
name,
done: false,
})
src/store/actions/index.js
export { todoDel, todoChangeDone } from './todo'
src/store/reducers/todo.js
import { TODO_ADD, TODO_CHANGE_DONE, TODO_DEL } from '../constants'
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id)
case TODO_CHANGE_DONE:
return state.map((item) =>
item.id === action.id
? {
...item,
done: !item.done,
}
: item
)
case TODO_ADD:
const { type, ...rest } = action
return [rest, ...state]
default:
return state
}
}
TodoHeader
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { todoAdd } from '../../store/actions/todo'
export default function TodoHeader() {
const [name, setName] = useState('')
const dispatch = useDispatch()
const handleChange = (e) => setName(e.target.value)
const handleKeyUp = (e) => {
if (e.key === 'Enter') {
if (name.trim().length === 0) return
dispatch(todoAdd(name))
setName('')
}
}
return (
<header className='header'>
<h1>todos</h1>
<input
className='new-todo'
placeholder='What needs to be done?'
autoFocus
value={name}
onChange={handleChange}
onKeyUp={handleKeyUp}
/>
</header>
)
}
TodoMain
import { useSelector, useDispatch } from 'react-redux'
import classNames from 'classnames'
import { todoChangeDone, todoDel } from '../../store/actions/todo'
// 常量 => actionCreator => action(常量) => Reducer => 组件
// 常爱瑞组:优秀!
export default function TodoMain() {
const dispatch = useDispatch()
// @ts-ignore
const lists = useSelector((state) => state.todo)
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
return (
<section className='main'>
<input id='toggle-all' className='toggle-all' type='checkbox' />
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<li
key={item.id}
className={classNames({
completed: item.done,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label>{item.name}</label>
<button
className='destroy'
onClick={() => handleDel(item.id)}
></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
))}
</ul>
</section>
)
}
全选和取消
src/store/constants/todo.js
export const TODO_DEL = 'TODO_DEL'
export const TODO_CHANGE_DONE = 'TODO_CHANGE_DONE'
export const TODO_ADD = 'TODO_ADD'
export const TODO_CHECK_ALL = 'TODO_CHECK_ALL'
src/store/constants/index.js
export { TODO_DEL, TODO_CHANGE_DONE, TODO_ADD, TODO_CHECK_ALL } from './todo'
src/store/actions/todo.js
import {
TODO_ADD,
TODO_CHANGE_DONE,
TODO_CHECK_ALL,
TODO_DEL,
} from '../constants'
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})
export const todoChangeDone = (id) => ({
type: TODO_CHANGE_DONE,
id,
})
export const todoAdd = (name) => ({
type: TODO_ADD,
id: Date.now(),
name,
done: false,
})
export const todoCheckAll = (done) => ({
type: TODO_CHECK_ALL,
done,
})
src/store/reducers/todo.js
import {
TODO_ADD,
TODO_CHANGE_DONE,
TODO_CHECK_ALL,
TODO_DEL,
} from '../constants'
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id)
case TODO_CHANGE_DONE:
return state.map((item) =>
item.id === action.id
? {
...item,
done: !item.done,
}
: item
)
case TODO_ADD:
const { type, ...rest } = action
return [rest, ...state]
case TODO_CHECK_ALL:
return state.map((item) => ({ ...item, done: action.done }))
default:
return state
}
}
TodoMain
import { useSelector, useDispatch } from 'react-redux'
import classNames from 'classnames'
import { todoChangeDone, todoCheckAll, todoDel } from '../../store/actions/todo'
// 常量 => actionCreator => action(常量) => Reducer => 组件
// 常爱瑞组:优秀!
export default function TodoMain() {
const dispatch = useDispatch()
// @ts-ignore
const lists = useSelector((state) => state.todo)
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
// !传递什么:当前状态的基础上进行取反
const handleChangeAll = () => dispatch(todoCheckAll(!nowStatus))
const nowStatus = lists.every((item) => item.done)
return (
<section className='main'>
<input
id='toggle-all'
className='toggle-all'
type='checkbox'
checked={nowStatus}
onChange={handleChangeAll}
/>
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<li
key={item.id}
className={classNames({
completed: item.done,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label>{item.name}</label>
<button
className='destroy'
onClick={() => handleDel(item.id)}
></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
))}
</ul>
</section>
)
}
双击展示编辑框
TodoMain
import { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import classNames from 'classnames'
import { todoChangeDone, todoCheckAll, todoDel } from '../../store/actions/todo'
export default function TodoMain() {
const dispatch = useDispatch()
// @ts-ignore
const lists = useSelector((state) => state.todo)
const [currentId, setCurrentId] = useState('')
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
// !传递什么:当前状态的基础上进行取反
const handleChangeAll = () => dispatch(todoCheckAll(!nowStatus))
const nowStatus = lists.every((item) => item.done)
// 1. 准备一个变量(状态)
// 2. 用变量和循环时候的 id 进行比较,一样的就使用 editing class
// 3. 双击的时候改变这个变量,改成当前双击时候的 id
const handleDblClick = (id) => setCurrentId(id)
return (
<section className='main'>
<input
id='toggle-all'
className='toggle-all'
type='checkbox'
checked={nowStatus}
onChange={handleChangeAll}
/>
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<li
key={item.id}
className={classNames({
completed: item.done,
editing: currentId === item.id,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label onDoubleClick={() => handleDblClick(item.id)}>
{item.name}
</label>
<button
className='destroy'
onClick={() => handleDel(item.id)}
></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
/>
</li>
))}
</ul>
</section>
)
}
双击聚焦输入框
ToDoItem
import classNames from 'classnames'
import { useState, useRef, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { todoChangeDone, todoDel } from '../../store/actions/todo'
export default function TodoItem({ item }) {
const inputRef = useRef(null)
const dispatch = useDispatch()
const [currentId, setCurrentId] = useState('')
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
const handleDblClick = (id) => setCurrentId(id)
const handleBlur = () => setCurrentId('')
useEffect(() => {
inputRef.current.focus()
}, [currentId])
return (
<li
className={classNames({
completed: item.done,
editing: currentId === item.id,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label onDoubleClick={() => handleDblClick(item.id)}>{item.name}</label>
<button className='destroy' onClick={() => handleDel(item.id)}></button>
</div>
<input
className='edit'
value='Create a TodoMVC template'
onChange={() => {}}
ref={inputRef}
onBlur={handleBlur}
/>
</li>
)
}
TodoMain
import { useSelector, useDispatch } from 'react-redux'
import { todoCheckAll } from '../../store/actions/todo'
import TodoItem from '../TodoItem'
export default function TodoMain() {
const dispatch = useDispatch()
// @ts-ignore
const lists = useSelector((state) => state.todo)
const handleChangeAll = () => dispatch(todoCheckAll(!nowStatus))
const nowStatus = lists.every((item) => item.done)
return (
<section className='main'>
<input
id='toggle-all'
className='toggle-all'
type='checkbox'
checked={nowStatus}
onChange={handleChangeAll}
/>
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<TodoItem item={item} key={item.id} />
))}
</ul>
</section>
)
}
双击回显
TodoItem
import classNames from 'classnames'
import { useState, useRef, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { todoChangeDone, todoDel } from '../../store/actions/todo'
export default function TodoItem({ item }) {
const inputRef = useRef(null)
const dispatch = useDispatch()
const [currentId, setCurrentId] = useState('')
const [currentName, setCurrentName] = useState('')
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
const handleDblClick = (id) => {
setCurrentId(id)
setCurrentName(item.name)
}
const handleBlur = () => setCurrentId('')
// 先把输入的数据挤下来,敲回车的时候再更新到 Redux
const handleEditChange = (e) => {
setCurrentName(e.target.value)
}
useEffect(() => {
inputRef.current.focus()
}, [currentId])
return (
<li
className={classNames({
completed: item.done,
editing: currentId === item.id,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label onDoubleClick={() => handleDblClick(item.id)}>{item.name}</label>
<button className='destroy' onClick={() => handleDel(item.id)}></button>
</div>
<input
className='edit'
value={currentName}
onChange={handleEditChange}
ref={inputRef}
onBlur={handleBlur}
/>
</li>
)
}
修改Todo
src/store/constants/todo.js
export const TODO_DEL = 'TODO_DEL'
export const TODO_CHANGE_DONE = 'TODO_CHANGE_DONE'
export const TODO_ADD = 'TODO_ADD'
export const TODO_CHECK_ALL = 'TODO_CHECK_ALL'
export const TODO_MODIFY_NAME = 'TODO_MODIFY_NAME'
src/store/constants/index.js
export {
TODO_DEL,
TODO_CHANGE_DONE,
TODO_ADD,
TODO_CHECK_ALL,
TODO_MODIFY_NAME,
} from './todo'
src/store/actions/todo.js
import {
TODO_ADD,
TODO_CHANGE_DONE,
TODO_CHECK_ALL,
TODO_DEL,
TODO_MODIFY_NAME,
} from '../constants'
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})
export const todoChangeDone = (id) => ({
type: TODO_CHANGE_DONE,
id,
})
export const todoAdd = (name) => ({
type: TODO_ADD,
id: Date.now(),
name,
done: false,
})
export const todoCheckAll = (done) => ({
type: TODO_CHECK_ALL,
done,
})
export const todoModifyName = (id, name) => ({
type: TODO_MODIFY_NAME,
id,
name,
})
src/store/actions/index.js
export {
todoDel,
todoChangeDone,
todoAdd,
todoCheckAll,
todoModifyName,
} from './todo'
src/store/reducers/todo.js
import {
TODO_ADD,
TODO_CHANGE_DONE,
TODO_CHECK_ALL,
TODO_DEL,
TODO_MODIFY_NAME,
} from '../constants'
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id)
case TODO_CHANGE_DONE:
return state.map((item) =>
item.id === action.id
? {
...item,
done: !item.done,
}
: item
)
case TODO_ADD:
const { type, ...rest } = action
return [rest, ...state]
case TODO_CHECK_ALL:
return state.map((item) => ({ ...item, done: action.done }))
case TODO_MODIFY_NAME:
return state.map((item) =>
item.id === action.id
? {
...item,
name: action.name,
}
: item
)
default:
return state
}
}
TodoItem
import classNames from 'classnames'
import { useState, useRef, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { todoChangeDone, todoDel, todoModifyName } from '../../store/actions'
export default function TodoItem({ item }) {
const inputRef = useRef(null)
const dispatch = useDispatch()
const [currentId, setCurrentId] = useState('')
const [currentName, setCurrentName] = useState('')
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
const handleDblClick = (id) => {
setCurrentId(id)
setCurrentName(item.name)
}
const handleBlur = () => setCurrentId('')
// 先把输入的数据挤下来,敲回车的时候再更新到 Redux
const handleEditChange = (e) => {
setCurrentName(e.target.value)
}
const handleKeyUp = (e) => {
if (e.key === 'Enter') {
dispatch(todoModifyName(item.id, currentName))
setCurrentId('')
setCurrentName('')
}
}
useEffect(() => {
inputRef.current.focus()
}, [currentId])
return (
<li
className={classNames({
completed: item.done,
editing: currentId === item.id,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label onDoubleClick={() => handleDblClick(item.id)}>{item.name}</label>
<button className='destroy' onClick={() => handleDel(item.id)}></button>
</div>
<input
className='edit'
value={currentName}
onChange={handleEditChange}
ref={inputRef}
onBlur={handleBlur}
onKeyUp={handleKeyUp}
/>
</li>
)
}
清空已完成
src/store/constants/todo.js
export const TODO_DEL = 'TODO_DEL'
export const TODO_CHANGE_DONE = 'TODO_CHANGE_DONE'
export const TODO_ADD = 'TODO_ADD'
export const TODO_CHECK_ALL = 'TODO_CHECK_ALL'
export const TODO_MODIFY_NAME = 'TODO_MODIFY_NAME'
export const TODO_CLEAR_DONED = 'TODO_CLEAR_DONED'
src/store/constants/index.js
export {
TODO_DEL,
TODO_CHANGE_DONE,
TODO_ADD,
TODO_CHECK_ALL,
TODO_MODIFY_NAME,
TODO_CLEAR_DONED,
} from './todo'
src/store/actions/todo.js
import {
TODO_ADD,
TODO_CHANGE_DONE,
TODO_CHECK_ALL,
TODO_CLEAR_DONED,
TODO_DEL,
TODO_MODIFY_NAME,
} from '../constants'
export const todoDel = (id) => ({
type: TODO_DEL,
id,
})
export const todoChangeDone = (id) => ({
type: TODO_CHANGE_DONE,
id,
})
export const todoAdd = (name) => ({
type: TODO_ADD,
id: Date.now(),
name,
done: false,
})
export const todoCheckAll = (done) => ({
type: TODO_CHECK_ALL,
done,
})
export const todoModifyName = (id, name) => ({
type: TODO_MODIFY_NAME,
id,
name,
})
export const todoClearDoned = () => ({
type: TODO_CLEAR_DONED,
})
src/store/reducers/todo.js
import {
TODO_ADD,
TODO_CHANGE_DONE,
TODO_CHECK_ALL,
TODO_CLEAR_DONED,
TODO_DEL,
TODO_MODIFY_NAME,
} from '../constants'
const initState = [
{
id: 1,
name: '吃饭',
done: true,
},
{
id: 2,
name: '睡觉',
done: false,
},
]
export default function todo(state = initState, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id)
case TODO_CHANGE_DONE:
return state.map((item) =>
item.id === action.id
? {
...item,
done: !item.done,
}
: item
)
case TODO_ADD:
const { type, ...rest } = action
return [rest, ...state]
case TODO_CHECK_ALL:
return state.map((item) => ({ ...item, done: action.done }))
case TODO_MODIFY_NAME:
return state.map((item) =>
item.id === action.id
? {
...item,
name: action.name,
}
: item
)
case TODO_CLEAR_DONED:
return state.filter((item) => !item.done)
default:
return state
}
}
TodoFooter
import { useDispatch } from 'react-redux'
import { todoClearDoned } from '../../store/actions/todo'
export default function TodoFooter() {
const dispatch = useDispatch()
const handleClearDoned = () => dispatch(todoClearDoned())
return (
<footer className='footer'>
<span className='todo-count'>
<strong>0</strong> item left
</span>
<ul className='filters'>
<li>
<a className='selected' href='#/'>
All
</a>
</li>
<li>
<a href='#/active'>Active</a>
</li>
<li>
<a href='#/completed'>Completed</a>
</li>
</ul>
<button className='clear-completed' onClick={handleClearDoned}>
Clear completed
</button>
</footer>
)
}
剩余数量的统计
TodoFooter
import { useDispatch, useSelector } from 'react-redux'
import { todoClearDoned } from '../../store/actions/todo'
export default function TodoFooter() {
const dispatch = useDispatch()
// @ts-ignore
const lists = useSelector((state) => state.todo)
const leftCount = lists.filter((item) => !item.done).length
const handleClearDoned = () => dispatch(todoClearDoned())
return (
<footer className='footer'>
<span className='todo-count'>
<strong>{leftCount}</strong> item left
</span>
<ul className='filters'>
<li>
<a className='selected' href='#/'>
All
</a>
</li>
<li>
<a href='#/active'>Active</a>
</li>
<li>
<a href='#/completed'>Completed</a>
</li>
</ul>
<button className='clear-completed' onClick={handleClearDoned}>
Clear completed
</button>
</footer>
)
}
点击高亮
src/store/constants/filter.js
export const FILTER_ACTIVE = 'FILTER_ACTIVE'
src/store/constants/index.js
export {
TODO_DEL,
TODO_CHANGE_DONE,
TODO_ADD,
TODO_CHECK_ALL,
TODO_MODIFY_NAME,
TODO_CLEAR_DONED,
} from './todo'
export { FILTER_ACTIVE } from './filter'
src/store/actions/todo.js
import { FILTER_ACTIVE } from '../constants'
export const filterActive = (active) => ({
type: FILTER_ACTIVE,
active,
})
src/store/reducers/filter.js
import { FILTER_ACTIVE } from '../constants'
const initState = {
arr: ['all', 'active', 'completed'],
active: 'all',
}
export default function filter(state = initState, action) {
switch (action.type) {
case FILTER_ACTIVE:
return {
...state,
active: action.active,
}
default:
return state
}
}
src/store/reducers/index.js
// 组合 reducers
import { combineReducers } from 'redux'
import todo from './todo'
import filter from './filter'
export default combineReducers({ todo, filter })
TodoFooter
import classNames from 'classnames'
import { useDispatch, useSelector } from 'react-redux'
import { filterActive } from '../../store/actions/filter'
import { todoClearDoned } from '../../store/actions/todo'
export default function TodoFooter() {
const dispatch = useDispatch()
// @ts-ignore
const lists = useSelector((state) => state.todo)
// @ts-ignore
const { arr, active } = useSelector((state) => state.filter)
const leftCount = lists.filter((item) => !item.done).length
const handleClearDoned = () => dispatch(todoClearDoned())
const handleActive = (item) => dispatch(filterActive(item))
return (
<footer className='footer'>
<span className='todo-count'>
<strong>{leftCount}</strong> item left
</span>
<ul className='filters'>
{arr.map((item) => (
<li key={item} onClick={() => handleActive(item)}>
<a
className={classNames({
selected: active === item,
})}
href='#/'
>
{item}
</a>
</li>
))}
</ul>
<button className='clear-completed' onClick={handleClearDoned}>
Clear completed
</button>
</footer>
)
}
切换功能
src/components/TodoMain/index.js
import { useSelector, useDispatch } from 'react-redux'
import { todoCheckAll } from '../../store/actions/todo'
import TodoItem from '../TodoItem'
export default function TodoMain() {
const dispatch = useDispatch()
const lists = useSelector((state) => {
// @ts-ignore
const ac = state.filter.active
if (ac === 'active') {
// @ts-ignore
return state.todo.filter((item) => !item.done)
}
if (ac === 'completed') {
// @ts-ignore
return state.todo.filter((item) => item.done)
}
// @ts-ignore
return state.todo
})
const handleChangeAll = () => dispatch(todoCheckAll(!nowStatus))
const nowStatus = lists.every((item) => item.done)
return (
<section className='main'>
<input
id='toggle-all'
className='toggle-all'
type='checkbox'
checked={nowStatus}
onChange={handleChangeAll}
/>
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<TodoItem item={item} key={item.id} />
))}
</ul>
</section>
)
}
完成效果
TodoFooter
import { useSelector, useDispatch } from 'react-redux'
import { todoCheckAll } from '../../store/actions/todo'
import TodoItem from '../TodoItem'
export default function TodoMain() {
const dispatch = useDispatch()
const lists = useSelector((state) => {
// @ts-ignore
const ac = state.filter.active
if (ac === 'active') {
// @ts-ignore
return state.todo.filter((item) => !item.done)
}
if (ac === 'completed') {
// @ts-ignore
return state.todo.filter((item) => item.done)
}
// @ts-ignore
return state.todo
})
const handleChangeAll = () => dispatch(todoCheckAll(!nowStatus))
const nowStatus = lists.every((item) => item.done)
return (
<section className='main'>
<input
id='toggle-all'
className='toggle-all'
type='checkbox'
checked={nowStatus}
onChange={handleChangeAll}
/>
<label htmlFor='toggle-all'>Mark all as complete</label>
<ul className='todo-list'>
{lists.map((item) => (
<TodoItem item={item} key={item.id} />
))}
</ul>
</section>
)
}
TodoItem
import classNames from 'classnames'
import { useState, useRef, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { todoChangeDone, todoDel, todoModifyName } from '../../store/actions'
export default function TodoItem({ item }) {
const inputRef = useRef(null)
const dispatch = useDispatch()
const [currentId, setCurrentId] = useState('')
const [currentName, setCurrentName] = useState('')
const handleDel = (id) => dispatch(todoDel(id))
const handleChange = (id) => dispatch(todoChangeDone(id))
const handleDblClick = (id, name) => {
setCurrentId(id)
setCurrentName(name)
}
const handleBlur = () => setCurrentId('')
// 先把输入的数据挤下来,敲回车的时候再更新到 Redux
const handleEditChange = (e) => setCurrentName(e.target.value)
const handleKeyUp = (e) => {
if (e.key === 'Escape') return handleDblClick('', '')
if (e.key === 'Enter') {
dispatch(todoModifyName(item.id, currentName))
handleDblClick('', '')
}
}
useEffect(() => inputRef.current.focus(), [currentId])
return (
<li
className={classNames({
completed: item.done,
editing: currentId === item.id,
})}
>
<div className='view'>
<input
className='toggle'
type='checkbox'
checked={item.done}
onChange={() => handleChange(item.id)}
/>
<label onDoubleClick={() => handleDblClick(item.id, item.name)}>
{item.name}
</label>
<button className='destroy' onClick={() => handleDel(item.id)}></button>
</div>
<input
className='edit'
value={currentName}
onChange={handleEditChange}
ref={inputRef}
onBlur={handleBlur}
onKeyUp={handleKeyUp}
/>
</li>
)
}
Redux数据持久化
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'
import './styles/base.css'
import './styles/index.css'
import App from './App'
import store from './store'
const persistor = persistStore(store)
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.querySelector('#root')
)
src/store/index.js
import { createStore } from 'redux'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducers'
const persistConfig = { key: '@TODO@', storage }
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default createStore(persistedReducer, composeWithDevTools())
异步处理
src/store/actions/filter.js
import { FILTER_ACTIVE } from '../constants'
export const filterActiveAc = (active) => ({ type: FILTER_ACTIVE, active })
export const filterActive = (active) => {
// 一旦配置 redux-thunk 中间件,这里就支持返回返回的形式啦
return (dispatch) => {
setTimeout(() => {
dispatch(filterActiveAc(active))
}, 2000)
}
}
src/store/index.js
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducers'
const persistConfig = { key: '@TODO@', storage }
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default createStore(
persistedReducer,
composeWithDevTools(applyMiddleware(thunk))
)
src/store/index.js
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducers'
const persistConfig = { key: '@TODO@', storage }
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default createStore(
persistedReducer,
composeWithDevTools(applyMiddleware(thunk))
)