最近在学React,跟着写了点东西,这个案例能够学习到的东西:
1. 受控组件;
2. 列表渲染;
3. setState传入一个参数是什么效果;
接下来请看代码:
import React, { Component } from 'react'
interface ListItemI {
id: number;
text: string;
completed: boolean;
editable: boolean;
}
interface StateI {
list: ListItemI[];
inputValue: string;
}
export default class TodoListControled extends Component<{}, StateI> {
constructor(props: any) {
super(props)
this.state = {
list: [],
inputValue: ''
}
}
// 添加事件项
addListItem() {
if (this.state.inputValue) {
const text = this.state.inputValue;
// 可以写一个自增的函数用作id
const newListItem = { id: Date.now(), text, completed: false, editable: false };
this.setState({
list: [...this.state.list, newListItem]
})
this.setState({
inputValue: ''
})
}
}
// 删除事件项
deleteListItem(id: number) {
// 删除方式一:通过filter,缺点是耗费性能
// this.setState({
// list: this.state.list.filter(item => item.id !== id)
// })
// 删除方法二:通过查找index,使用slice,这个index可以通过map的第二个参数传进来
const index = this.state.list.findIndex(item => item.id === id);
const newList = [...this.state.list.slice(0, index), ...this.state.list.slice(index + 1)];
this.setState({
list: newList
})
}
// 点击完成事件项
completeListItem(id: number) {
this.setState({
list: this.state.list.map(item => item.id === id ? { ...item, completed: !item.completed } : item)
})
}
// 切换表单编辑状态
switchEditListItem(item: ListItemI) {
this.setState(prevState => ({
list: prevState.list.map(i => {
if (i.id === item.id) {
return {
...i,
editable: !i.editable
};
}
return i;
})
}));
}
// 修改事件项
editListItem(e: React.ChangeEvent<HTMLInputElement>, item: ListItemI) {
const newValue = e.target.value;
this.setState(prevState => ({
list: prevState.list.map(i => {
if (i.id === item.id) {
return {
...i,
text: newValue
};
}
return i;
})
}));
}
render() {
// 无数据的空状态
const tempy = <div className='w-full h-[80px] bg-gray-200 flex justify-center items-center'>
暂无数据
</div>
// 列表渲染
const listDom = this.state.list.map(item => {
return <li key={item.id} className='h-[40px] bg-[#ccc] border-[1px] rounded-md my-2 flex flex-row items-center justify-around px-2'>
<input className='mr-4' type="checkbox" checked={item.completed} onChange={(e) => {
this.completeListItem(item.id)
}} />
{
item.editable ? <input type="text" value={item.text}
onChange={(e) => {
this.editListItem(e, item)
}} onKeyDown={(e) => {
if (e.key === 'Enter') {
this.switchEditListItem(item)
}
}} />
: <span className='text-[16px] text-[#333] flex-1'>{item.text}</span>
}
<span className='text-[16px] text-[#333] mr-4'>{item.completed ? '😆' : '😔'}</span>
<button className='bg-[#ff6d9c] h-[20px] mr-4 text-[#ffffff] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => this.deleteListItem(item.id)}>删除</button>
<button className='bg-[#fcfcfc] h-[20px] text-[#666] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => this.switchEditListItem(item)}>修改</button>
</li>
})
return (
<div className='mx-auto mt-[200px] w-[800px] px-4 py-4 flex flex-col gap-3'>
<div className='flex flex-row w-full justify-between gap-5'>
<input className='border flex-1' type="text" value={this.state.inputValue} onChange={(e) => {
this.setState({
inputValue: e.target.value
})
}} onKeyDown={
(e) => {
if (e.key === 'Enter') {
this.addListItem()
}
}
} />
<button className='bg-[#fcfcfc] h-[20px] text-[#666] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => this.addListItem()}>添加</button>
</div>
<ul className='w-full'>
{/* 条件渲染,如果list没有数据就显示暂无数据 */}
{this.state.list.length > 0 ? listDom : tempy}
</ul>
<div className='text-[#333]'>
<span>已完成/{this.state.list.filter(item => item.completed === true).length}</span>
<span>未完成/{this.state.list.filter(item => item.completed === false).length}</span>
</div>
</div>
)
}
}
样式我用的是Tailwindcss,不想引入第三方库了,还希望大佬多多担待,慢慢学习中......
学完了函数组件的useState后用函数式组件重写了一下,并且把ListItem提出来了,加入了Porps通信,其实还能在加插槽的相关练习,不过就先这样,感兴趣的小伙伴可以自己试着用插槽,并不会改变代码量,只是可读性和可维护性能好点。代码如下啊
import React, { useState } from 'react'
interface ListItemI {
id: number;
text: string;
completed: boolean;
editable: boolean;
}
type ListItemDomProps = ListItemI & {
completeListItem: (id: number) => void;
deleteListItem: (id: number) => void;
editListItem: (e: React.ChangeEvent<HTMLInputElement>, item: ListItemI) => void;
switchEditListItem: (item: ListItemI) => void;
}
function ListItemDom(props: ListItemDomProps) {
const { completeListItem, deleteListItem, editListItem, switchEditListItem } = props;
return (
<li key={props.id} className='h-[40px] bg-[#ccc] border-[1px] rounded-md my-2 flex flex-row items-center justify-around px-2'>
<input className='mr-4' type="checkbox" checked={props.completed} onChange={(e) => {
completeListItem(props.id)
}} />
{
props.editable ? <input type="text" value={props.text}
onChange={(e) => {
editListItem(e, props)
}} onKeyDown={(e) => {
if (e.key === 'Enter') {
switchEditListItem(props)
}
}} />
: <span className='text-[16px] text-[#333] flex-1'>{props.text}</span>
}
<span className='text-[16px] text-[#333] mr-4'>{props.completed ? '😆' : '😔'}</span>
<button className='bg-[#ff6d9c] h-[20px] mr-4 text-[#ffffff] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => deleteListItem(props.id)}>删除</button>
<button className='bg-[#fcfcfc] h-[20px] text-[#666] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => switchEditListItem(props)}>修改</button>
</li>
)
}
export default function TodoListFunCom() {
const [dataList, setDataList] = useState<ListItemI[]>([])
const [inputValue, setInputValue] = useState<string>('')
// 添加事件项
const addListItem = () => {
if (inputValue) {
const newListItem = [...dataList, { id: Date.now(), text: inputValue, completed: false, editable: false }]
setDataList(newListItem)
setInputValue('')
}
}
// 删除事件项
const deleteListItem = (id: number) => {
const index = dataList.findIndex(item => item.id === id);
const newList = [...dataList.slice(0, index), ...dataList.slice(index + 1)];
setDataList(newList)
}
// 点击完成事件项
const completeListItem = (id: number) => {
setDataList(dataList.map(item => item.id === id ? { ...item, completed: !item.completed } : item))
}
// 切换表单编辑状态
const switchEditListItem = (item: ListItemI) => {
const index = dataList.indexOf(item)
const newList = dataList.map((ele, i) => {
if (i === index) {
return { ...ele, editable: !ele.editable }
} else {
return ele
}
})
setDataList(newList)
}
// 修改事件项
const editListItem = (e: React.ChangeEvent<HTMLInputElement>, item: ListItemI) => {
setInputValue(e.target.value)
const index = dataList.indexOf(item)
const newList = dataList.map((ele, i) => {
if (i === index) {
return { ...ele, text: inputValue, editable: !ele.editable }
} else {
return ele
}
})
setDataList(newList)
}
// 无数据的空状态
const tempy = <div className='w-full h-[80px] bg-gray-200 flex justify-center items-center'>
暂无数据
</div>
// 列表渲染
const listDom = dataList.map((item, index) => {
return <ListItemDom key={item.id} deleteListItem={deleteListItem} editListItem={editListItem} switchEditListItem={switchEditListItem} completeListItem={completeListItem} id={item.id} text={item.text} completed={item.completed} editable={item.editable}></ListItemDom>
})
return (
<div className='mx-auto mt-[200px] w-[800px] px-4 py-4 flex flex-col gap-3'>
<div className='flex flex-row w-full justify-between gap-5'>
<input className='border flex-1' type="text" value={inputValue} onChange={(e) => {
setInputValue(e.target.value)
}} onKeyDown={
(e) => { if (e.key === 'Enter') { addListItem() } }
} />
<button className='bg-[#fcfcfc] h-[20px] text-[#666] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => addListItem()}>添加</button>
</div>
<ul className='w-full'>
{/* 条件渲染,如果list没有数据就显示暂无数据 */}
{dataList.length > 0 ? listDom : tempy}
</ul>
<div className='text-[#333]'>
<span>已完成/{dataList.filter(item => item.completed === true).length}</span>
<span>未完成/{dataList.filter(item => item.completed === false).length}</span>
</div>
</div>
)
}
学完useReducer之后再来一个版本,这个版本理解起来比较麻烦,但是相信大佬应该没有看不懂的。首先这样做的好处是状态集中式管理,物理逻辑与视图逻辑解耦,代码的可维护性更高,其实当我们所处理的逻辑更加复杂的情况下可以写一个工具函数专门用于处理datalist数据,这样子我们只需要在相应的时机去调用就好了,代码也不会很长,这里我就不做处理了,也是刚学习React16版本之后,后面有案例也会更新在这里.
import React, { useContext, useReducer } from 'react'
interface ListItemI {
id: number;
text: string;
completed: boolean;
editable: boolean;
}
interface InitValueI {
dataList: ListItemI[] | null;
inputValue: string;
}
function ListItemDom(props: ListItemI) {
const context: any = useContext(globalContext)
const { dispatch } = context
return (
<li key={props.id} className='h-[40px] bg-[#ccc] border-[1px] rounded-md my-2 flex flex-row items-center justify-around px-2'>
<input className='mr-4' type="checkbox" checked={props.completed} onChange={(e) => {
dispatch({
type: 'completeListItem',
id: props.id
})
}} />
{
props.editable ? <input type="text" value={props.text}
onChange={(e) => {
dispatch({
type: 'editListItem',
value: e.target.value,
id: props.id
})
}} onKeyDown={(e) => {
if (e.key === 'Enter') {
dispatch({
type: 'switchEditListItem',
id: props.id
})
}
}} />
: <span className='text-[16px] text-[#333] flex-1'>{props.text}</span>
}
<span className='text-[16px] text-[#333] mr-4'>{props.completed ? '😆' : '😔'}</span>
<button className='bg-[#ff6d9c] h-[20px] mr-4 text-[#ffffff] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => {
dispatch({
type: 'deleteListItem',
id: props.id
})
}}>删除</button>
<button className='bg-[#fcfcfc] h-[20px] text-[#666] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => {
dispatch({
type: 'switchEditListItem',
id: props.id
})
}}>{props.editable ? '确认修改' : '修改'}</button>
</li>
)
}
// 定义一个全局的状态
const initralValue: InitValueI = {
dataList: [],
inputValue: '',
}
// 对状态进行处理
const reducer = (preState: InitValueI, action: any): InitValueI => {
const newState = { ...preState }
if (!newState.dataList) return newState
let index = 0
switch (action.type) {
case 'addListItem':
return {
dataList: [...newState.dataList, { id: Date.now(), text: newState.inputValue, completed: false, editable: false }],
inputValue: '',
}
case 'deleteListItem':
index = newState.dataList.findIndex(item => item.id === action.id);
return {
dataList: [...newState.dataList.slice(0, index), ...newState.dataList.slice(index + 1)],
inputValue: '',
}
case 'completeListItem':
return {
dataList: newState.dataList.map(item => item.id === action.id ? { ...item, completed: !item.completed } : item),
inputValue: '',
}
case 'switchEditListItem':
index = newState.dataList.findIndex(item => item.id === action.id);
return {
dataList: newState.dataList.map((ele, i) => {
if (i === index) {
return { ...ele, editable: !ele.editable }
} else {
return ele
}
}),
inputValue: '',
}
case 'editListItem':
index = newState.dataList.findIndex(item => item.id === action.id);
return {
dataList: newState.dataList.map((ele, i) => {
if (i === index) {
return { ...ele, text: action.value, editable: true }
} else {
return ele
}
}),
inputValue: '',
}
case 'changeInputValue':
return {
dataList: newState.dataList,
inputValue: action.value,
}
default:
return newState
}
}
// 创建一个Provider,用于分发state和dispatch
const globalContext = React.createContext({})
export default function TodoListFunCom() {
// 使用useReducer将状态集中管理,reducer函数没人看不懂吧,有的话留言或者私信.
const [state, dispatch] = useReducer<React.Reducer<InitValueI, any>>(reducer, initralValue);
// 无数据的空状态
const tempy = <div className='w-full h-[80px] bg-gray-200 flex justify-center items-center'>
暂无数据
</div>
// 列表渲染
const listDom = state.dataList!.map((item, index) => {
return <ListItemDom key={item.id} id={item.id} text={item.text} editable={item.editable} completed={item.completed}></ListItemDom>
})
return (
<div className='mx-auto mt-[200px] w-[800px] px-4 py-4 flex flex-col gap-3'>
<globalContext.Provider value={
{
state, dispatch
}
}>
<div className='flex flex-row w-full justify-between gap-5'>
<input className='border flex-1' type="text" value={state.inputValue} onChange={(e) => {
dispatch({
type: 'changeInputValue',
value: e.target.value
})
}} onKeyDown={
(e) => {
if (e.key === 'Enter') {
dispatch({
type: 'addListItem'
})
}
}
} />
<button className='bg-[#fcfcfc] h-[20px] text-[#666] border px-5 py-3 rounded-sm flex items-center justify-center' onClick={() => dispatch({
type: 'addListItem'
})}>添加</button>
</div>
<ul className='w-full'>
{state.dataList!.length > 0 ? listDom : tempy}
</ul>
<div className='text-[#333]'>
<span>已完成/{state.dataList!.filter(item => item.completed === true).length}</span>
<span>未完成/{state.dataList!.filter(item => item.completed === false).length}</span>
</div>
</globalContext.Provider>
</div >
)
}