使用React實(shí)現(xiàn)一個(gè)簡(jiǎn)單的待辦事項(xiàng)列表的示例代碼
一、介紹
主要功能
用戶可以添加、編輯和刪除待辦事項(xiàng)。
效果展示
這篇文章我們將詳細(xì)講解如何建立一個(gè)這樣簡(jiǎn)單的列表。
二、編碼
(一)搭建項(xiàng)目、劃分組件
第一步,使用 npm i -g create-react-app
全局安裝腳手架。
第二步,使用 create-react-app staying-to-do
創(chuàng)建一個(gè)叫staying-to-do的項(xiàng)目文件,這里的文件名字大家可以自由定義。
注意: 這里自定義的文件名不要包含大寫,不然會(huì)報(bào)錯(cuò)。
第三步,進(jìn)入項(xiàng)目文件夾: cd hello-react
第四步,啟動(dòng)項(xiàng)目: npm start
第四步完成之后會(huì)自動(dòng)打開(kāi)一個(gè)react頁(yè)面,頁(yè)面中會(huì)有一個(gè)一個(gè)旋轉(zhuǎn)的react大loge就算啟動(dòng)成功了!
第五步 劃分組件。
總所周知,react最重要的就是組件,這里很顯然是添加待辦的頭部、統(tǒng)計(jì)和刪除的底部、具體展示的中部列表和列表中的每個(gè)小展示條,這四個(gè)組件的文件夾分別命名為 Header
、 Footer
、 List
、 Item
。如下圖所示:
每個(gè)組件都是由一個(gè) index.jsx
和 index.css
構(gòu)成,把所有組件都放在一個(gè)叫 components
的文件夾中,再把 components
放到文件夾 src
方便以后查找,最后形成的項(xiàng)目目錄應(yīng)該如下:
當(dāng)然,我這樣的做法并不是一定的,大家可以根據(jù)自己的思路劃分組件。
(二)分析思路、直接開(kāi)干
傳值方式
很顯然,這個(gè)項(xiàng)目最有練習(xí)意義的就是組件之間的傳值,例如Header組件輸入的數(shù)據(jù)要在Item組件中展現(xiàn),也就是要實(shí)現(xiàn)兄弟組件之間的傳值。我們都知道利用組件三大屬性中的 state
和 props
就能實(shí)現(xiàn)父子組件之間的傳值,但是如何實(shí)現(xiàn)兄弟組件之間的傳值呢,或許大家都有學(xué)過(guò)一些消息訂閱啊hook的方法,但是這里我將教大家用最原始的方法實(shí)現(xiàn),或許這不是在世紀(jì)開(kāi)發(fā)中最常用的方法,但一定能在初學(xué)之時(shí)幫助我們更好的練習(xí)react的相關(guān)特性。
我的方法是把要使用的數(shù)據(jù)放在所有組件的父親——App組件中,然后利用 state
和 props
傳給子組件,這樣就用父子組件傳值實(shí)現(xiàn)了兄弟組件的傳值。使用這個(gè)方法,父親組件要預(yù)先使用props屬性向子組件傳遞一個(gè)函數(shù),子組件在調(diào)用這個(gè)函數(shù)獲取父親組件保存的數(shù)據(jù)(也就是俗稱的 狀態(tài)在哪里,操作狀態(tài)的方法就在哪里 )
開(kāi)始編寫
App.jsx文件中,
第一,用一個(gè) todos
的保存待辦事項(xiàng),每個(gè)元素都是一個(gè)對(duì)象,每個(gè)對(duì)象包括待辦事項(xiàng)的序號(hào)、待辦事項(xiàng)的名稱和是否完成的標(biāo)志。
第二,引入要用的組件,例如 import Footer from './components/Footer'
。布局,把組件放在該放的地方。引入全局樣式App.css,里面具體寫啥咱后面再說(shuō)
第三,分析全局要實(shí)現(xiàn)的功能,定義相關(guān)的函數(shù),然后把函數(shù)用 props
傳給子組件,供子組件使用。例如 Header
組件有一個(gè)輸入回車添加待辦事件的功能,本質(zhì)上就是接受一個(gè)新的對(duì)象,然后更新?tīng)顟B(tài)里的 todos
,我們把這個(gè)函數(shù) addTodo
在App.jsx中定義好,然后傳遞給子組件使用 <Header addTodo={this.addTodo}/>
所以,整個(gè)App.jsx的代碼如下:
import React, { Component } from 'react' import Header from './components/Header' import List from './components/List' import Footer from './components/Footer' import './App.css' export default class App extends Component { //狀態(tài)在哪里,操作狀態(tài)的方法就在哪里 //初始化狀態(tài) state = {todos:[ {id:'001',name:'吃飯',done:true}, {id:'002',name:'睡覺(jué)',done:true}, {id:'003',name:'打代碼',done:false}, {id:'004',name:'看書(shū)',done:false} ]} //addTodo用于添加一個(gè)todo,接收的參數(shù)是todo對(duì)象 addTodo = (todoObj)=>{ //獲取原todos const {todos} = this.state //追加一個(gè)todo const newTodos = [todoObj,...todos] //更新?tīng)顟B(tài) this.setState({todos:newTodos}) } //updateTodo用于更新一個(gè)todo對(duì)象 updateTodo = (id,done)=>{ //獲取狀態(tài)中的todos const {todos} = this.state //匹配處理數(shù)據(jù) const newTodos = todos.map((todoObj)=>{ if(todoObj.id === id) return {...todoObj,done} else return todoObj }) this.setState({todos:newTodos}) } //deleteTodo用于刪除一個(gè)todo對(duì)象 deleteTodo = (id)=>{ //獲取原來(lái)的todos const {todos} = this.state //刪除指定id的todo對(duì)象 const newTodos = todos.filter((todoObj)=>{ return todoObj.id !== id }) //更新?tīng)顟B(tài) this.setState({todos:newTodos}) } //checkAllTodo用于全選 checkAllTodo = (done)=>{ //獲取原來(lái)的todos const {todos} = this.state //加工數(shù)據(jù) const newTodos = todos.map((todoObj)=>{ return {...todoObj,done} }) //更新?tīng)顟B(tài) this.setState({todos:newTodos}) } //clearAllDone用于清除所有已完成的 clearAllDone = ()=>{ //獲取原來(lái)的todos const {todos} = this.state //過(guò)濾數(shù)據(jù) const newTodos = todos.filter((todoObj)=>{ return !todoObj.done }) //更新?tīng)顟B(tài) this.setState({todos:newTodos}) } render() { const {todos} = this.state return ( <div className="todo-container"> <div className="todo-wrap"> <Header addTodo={this.addTodo}/> <List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/> <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/> </div> </div> ) } }
如你所見(jiàn),這里除了 addTodo
函數(shù),還有 Footer
用于統(tǒng)計(jì)數(shù)據(jù)的 checkAllTodo
和清除按鈕要用的 clearAllDone
,還有 Item
要用的更新的 updateTodo
和刪除按鈕要用的 deleteTodo
函數(shù),只不過(guò)特殊的是 Item
是 List
的子組件,需要通過(guò) List
獲取罷了。
Header組件中
index.jsx中,定義好一個(gè)輸入框之后最重要的就是獲取到輸入的內(nèi)容構(gòu)建一個(gè)對(duì)象,然后傳給父組件傳來(lái)的 addTodo
,實(shí)現(xiàn)這個(gè)功能的函數(shù)我們叫 handleKeyUp
,這個(gè)函數(shù)在 onKeyUp
按鍵彈起后觸發(fā), event
中的 target.value
和 keyCode
可以用于獲取輸入值和某一案件的鍵值。具體代碼如下:
import React, { Component } from 'react' import PropTypes from 'prop-types' import {nanoid} from 'nanoid' import './index.css' export default class Header extends Component { //對(duì)接收的props進(jìn)行:類型、必要性的限制 static propTypes = { addTodo:PropTypes.func.isRequired } //鍵盤事件的回調(diào) handleKeyUp = (event)=>{ //解構(gòu)賦值獲取keyCode,target const {keyCode,target} = event //判斷是否是回車按鍵 if(keyCode !== 13) return //添加的todo名字不能為空 if(target.value.trim() === ''){ alert('輸入不能為空') return } //準(zhǔn)備好一個(gè)todo對(duì)象 const todoObj = {id:nanoid(),name:target.value,done:false} //將todoObj傳遞給App this.props.addTodo(todoObj) //回車后清空輸入 target.value = '' } render() { return ( <div className="todo-header"> <input onKeyUp={this.handleKeyUp} type="text" placeholder="請(qǐng)輸入你的代辦事件名稱,按回車鍵確認(rèn)"/> </div> ) } }
在這里我們對(duì)父組件App.jsx傳過(guò)來(lái)的 addTodo
進(jìn)行類型限制,這里限制為 func
函數(shù)型,這里我們需要使用 npm install prop-types
額外在下載一個(gè)庫(kù)prop-types來(lái)限制。
在構(gòu)建對(duì)象的時(shí)候需要一個(gè)唯一的id,這里使用 npm install nanoid
安裝nanoid, nanoid
是一個(gè)生成唯一標(biāo)識(shí)符(UUID)的函數(shù)。它使用隨機(jī)算法生成短字符串,可以用于為應(yīng)用程序中的唯一標(biāo)識(shí)符生成惟一的、不可預(yù)測(cè)的值。
List組件中
index.jsx中要接收到父組件App.jsx傳來(lái)的todos,updateTodo,deleteTodo,然后引入子組件 Item
,再根據(jù)todos中的每個(gè)對(duì)象的id來(lái)渲染多少個(gè) Item
,代碼如下:
import React, { Component } from 'react' import PropTypes from 'prop-types' import Item from '../Item' import './index.css' export default class List extends Component { //對(duì)接收的props進(jìn)行:類型、必要性的限制 static propTypes = { todos:PropTypes.array.isRequired, updateTodo:PropTypes.func.isRequired, deleteTodo:PropTypes.func.isRequired, } render() { const {todos,updateTodo,deleteTodo} = this.props return ( <ul className="todo-main"> { todos.map( todo =>{ return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/> }) } </ul> ) } }
Item組件中
Item
組件中有一個(gè)很明顯的效果就是經(jīng)過(guò)哪一個(gè) Item
哪一個(gè) Item
就有一個(gè)高亮的效果,所以這里我們需要定義一個(gè)狀態(tài) mouse
來(lái)判斷鼠標(biāo)是否經(jīng)過(guò)某一 Item
,默認(rèn)為 false
不經(jīng)過(guò),因此需要定義一個(gè)由鼠標(biāo)進(jìn)入和鼠標(biāo)退出觸發(fā)的方法 handleMouse
來(lái)改變鼠標(biāo)的狀態(tài),index.jsx代碼如下:
import React, { Component } from 'react' import './index.css' export default class Item extends Component { state = {mouse:false} //標(biāo)識(shí)鼠標(biāo)移入、移出 //鼠標(biāo)移入、移出的回調(diào) handleMouse = (flag)=>{ return ()=>{ this.setState({mouse:flag}) } } //勾選、取消勾選某一個(gè)todo的回調(diào) handleCheck = (id)=>{ return (event)=>{ this.props.updateTodo(id,event.target.checked) } } //刪除一個(gè)todo的回調(diào) handleDelete = (id)=>{ if(window.confirm('確定刪除嗎?')){ this.props.deleteTodo(id) } } render() { const {id,name,done} = this.props const {mouse} = this.state return ( <li style={{backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}> <label> <input type="checkbox" checked={done} onChange={this.handleCheck(id)}/> <span>{name}</span> </label> <button onClick={()=> this.handleDelete(id) } className="btn btn-blue" style={{display:mouse?'block':'none'}}>刪除</button> </li> ) } }
每次勾選Item中的框框都要改變父組件App中的todos中對(duì)象的done屬性,這個(gè)方法叫 handleCheck
,由輸入框的是否改變事件觸發(fā);每次點(diǎn)擊Item末尾的按鈕都會(huì)觸發(fā) handleDelete
這個(gè)方法,它會(huì)把當(dāng)前Item的id傳給父組件的 deleteTodo
方法,通過(guò)這種方法刪除父組件中的todos里的對(duì)象。
Footer組件中
待辦事件總數(shù)我們直接用父組件App.jsx傳遞過(guò)來(lái)的todos的長(zhǎng)度來(lái)表示, index.jsx代碼如下:
import React, { Component } from 'react' import './index.css' export default class Footer extends Component { //全選checkbox的回調(diào) handleCheckAll = (event)=>{ this.props.checkAllTodo(event.target.checked) } //清除已完成任務(wù)的回調(diào) handleClearAllDone = ()=>{ this.props.clearAllDone() } render() { const {todos} = this.props //已完成的個(gè)數(shù) const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0) //總數(shù) const total = todos.length return ( <div className="todo-footer"> <label> <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/> </label> <span> <span>已完成{doneCount}</span> / 全部{total} </span> <button onClick={this.handleClearAllDone} className="btn btn-blue">清除已完成事件</button> </div> ) } }
這段代碼中想必大家看不懂的就只有 const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
這行,其實(shí)這段代碼沒(méi)什么了不起的,就是JavaScript中會(huì)用到的,下面我詳細(xì)講解一下:
todos
:是一個(gè)任務(wù)列表數(shù)組,每個(gè)任務(wù)都有一個(gè)done
屬性來(lái)表示任務(wù)是否已完成。reduce
:是一個(gè)數(shù)組方法,用于對(duì)數(shù)組的每個(gè)元素進(jìn)行累積計(jì)算。它接受兩個(gè)參數(shù):回調(diào)函數(shù)和初始值。(pre, todo) => pre + (todo.done ? 1 : 0)
:這是一個(gè)回調(diào)函數(shù),用于定義每個(gè)元素的累積計(jì)算規(guī)則。它接受兩個(gè)參數(shù):累積值(pre
)和當(dāng)前元素(todo
)。對(duì)于每個(gè)元素,如果todo.done
為真(即任務(wù)已完成),則返回pre + 1
,否則返回pre
。0
:這是reduce
方法的初始值,用于設(shè)置初始的累積值。
通過(guò)將每個(gè)已完成的任務(wù)計(jì)算為 1
,未完成的任務(wù)計(jì)算為 0
,然后累加所有元素的計(jì)算結(jié)果,即可得到已完成任務(wù)的數(shù)量。最后,將計(jì)算結(jié)果賦值給 doneCount
變量。
注意事項(xiàng)
我并沒(méi)有把代碼完全展現(xiàn)在文章中,例如每個(gè)組件的index.css和全局的App.css,這些是CSS的內(nèi)容,大家就自己變著花樣寫吧。
三、總結(jié)
這個(gè)小案例我們很好的實(shí)現(xiàn)了一個(gè)可以添加,刪除和編輯的待辦事項(xiàng)的案例,很好的練習(xí)了react的兩大屬性 state
和 props
,學(xué)會(huì)了 prop-types 和 nanoid 組件庫(kù),可見(jiàn)收獲多多。
以上就是使用React實(shí)現(xiàn)一個(gè)簡(jiǎn)單的待辦事項(xiàng)列表的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于React實(shí)現(xiàn)待辦事項(xiàng)列表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React通過(guò)hook實(shí)現(xiàn)封裝表格常用功能
這篇文章主要為大家詳細(xì)介紹了React通過(guò)hook封裝表格常用功能的使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-12-12React模擬實(shí)現(xiàn)Vue的keepAlive功能
Vue中,keep-alive組件可以緩存組件狀態(tài),在路由切換時(shí)重新掛載,實(shí)現(xiàn)這一功能在React中并不簡(jiǎn)單,但我們可以借助一個(gè)第三方庫(kù)——react-activation 來(lái)模擬Vue的keep-alive功能,需要的朋友可以參考下2024-10-10react?+?vite?+?ts項(xiàng)目中優(yōu)雅使用.svg文件
這篇文章主要為大家介紹了react?+?vite?+?ts項(xiàng)目中優(yōu)雅使用.svg文件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08解讀useState第二個(gè)參數(shù)的"第二個(gè)參數(shù)"
這篇文章主要介紹了useState第二個(gè)參數(shù)的"第二個(gè)參數(shù)",具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03react-pdf實(shí)現(xiàn)將pdf文件轉(zhuǎn)為圖片,用于頁(yè)面展示
這篇文章主要介紹了react-pdf實(shí)現(xiàn)將pdf文件轉(zhuǎn)為圖片,用于頁(yè)面展示問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07react-beautiful-dnd 實(shí)現(xiàn)組件拖拽功能
這篇文章主要介紹了react-beautiful-dnd 實(shí)現(xiàn)組件拖拽功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08使用reactjs優(yōu)化了進(jìn)度條頁(yè)面性能提高70%
這篇文章主要介紹了使用reactjs優(yōu)化了進(jìn)度條后頁(yè)面性能提高了70%的操作技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04React組件化的一些額外知識(shí)點(diǎn)補(bǔ)充
React是一個(gè)用于構(gòu)建用戶界面的JavaScript庫(kù),下面這篇文章主要給大家介紹了關(guān)于React組件化的一些額外知識(shí)點(diǎn),文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10