亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > WEB開發 > 正文

【React全家桶入門之九】圖書管理與自動完成

2024-04-27 15:12:04
字體:
來源:轉載
供稿:網友

圖書管理

還記得搭建項目的時候在db.json文件里寫的book嗎?

來回顧一下book這個“數據表”的結構:

{ ... "book": [ { "id": 10000, "name": "前端從入門到精通", "java從入門到放棄", "price": 1990, "owner_id": 10001 } ]}

除了id外,每本書都包含name、price和owner_id三個字段。

有了之前實現用戶管理的經驗,我們很快就可以實現圖書的增刪改查,兩者的代碼基本相同,此處不再贅述(代碼請查看項目代碼的C09_book版本)。

關于【用戶管理】與【圖書管理】之間相同的代碼,可以使用更高層次的高階組件來實現組件的配置化(留個坑抽空填)。若你有更好的想法,歡迎在博客中留言與我進一步探討。

圖書的添加/編輯有一個owner_id用來代表圖書的“所有者”,它的值是用戶表中一個用戶的id,一個用戶能擁有多本書,一本書只能被一個用戶擁有,這是一個一對多的關系。

上面提到的代碼中只是簡單地提供了一個輸入框來接收輸入的用戶ID,這樣的體驗并不夠好,我們來實現一個通用的自動完成組件。

自動完成組件

自動完成(英語:Auto-Complete)功能,指用戶在輸入一個字符串的部分內容時,就提供下拉菜單自動推薦相關常用字符串供用戶選擇以快速輸入的一項功能特性。

找了個例子看一下效果:

可以發現,這是一個包含一個輸入框、一個下拉框的復合控件。

實現一個通用組件,在動手寫代碼之前我會做以下準備工作:

確定組件結構觀察組件邏輯確定組件內部狀態(state)確定組件向外暴露的屬性(props)

組件結構

上面提了,這個組件由一個輸入框和一個下拉框組成。

注意,這里的下拉框是一個“偽”下拉框,并不是指select與option。仔細看上面的動圖,可以看得出來這個“偽”下拉框只是一個帶邊框的、位于輸入框正下方的一個列表。

我們可以假設組件的結構是這樣的:

<div> <input type="text"/> <ul> <li>...</li> ... </ul></div>

組件邏輯

觀察動圖,可以發現組件有以下行為:

未輸入時,與普通輸入框一致輸入改變時如果有建議的選項,則在下放顯示出建議列表建議列表可以使用鍵盤上下鍵進行選擇,選擇某一項時該項高亮顯示,并且輸入框的值變為該項的值當移出列表(在第一項按上鍵或在最后一項按下鍵)時,輸入框的值變為原來輸入的值(圖中的“as”)按下回車鍵可以確定選擇該項,列表消失可以使用鼠標在列表中進行選擇,鼠標移入的列表項高亮顯示

組件內部狀態

一個易用的通用組件應該對外隱藏只有內部使用的狀態。使用React組件的state來維護組件的內部狀態。

根據組件邏輯,我們可以確定自動完成組件需要這些內部狀態:

邏輯2|3|4:輸入框中顯示的值,默認為空字符串(displayValue)邏輯3|6:建議列表中高亮的項目,可以維護一個項目在列表中的索引,默認為-1(activeItemIndex)

組件暴露的屬性

我們的目標是一個通用的組件,所以類似組件實際的值、推薦列表這樣的狀態,應該由組件的使用者來控制:

如上圖,組件應向外暴露的屬性有:

value:代表實際的值(不同于上面的displayValue表示顯示的、臨時的值,value表示的是最終的值)options:代表當前組件的建議列表,為空數組時,建議列表隱藏onValueChange:用于在輸入值或確定選擇了某一項時通知使用者的回調方法,使用者可以在這個回調方法中對options、value進行更新

實現

確定了組件結構、組件邏輯、內部狀態和外部屬性之后,就可以著手進行編碼了:

/src/components下新建AutoComplete.js文件,寫入組件的基本代碼:

import React from 'react';class AutoComplete extends React.Component { constructor (props) { super(props); this.state = { displayValue: '', activeItemIndex: -1 }; } render () { const {displayValue, activeItemIndex} = this.state; const {value, options} = this.props; return ( <div> <input value={value}/> {options.length > 0 && ( <ul> { options.map((item, index) => { return ( <li key={index}> {item.text || item} </li> ); }) } </ul> )} </div> ); }}// 通用組件最好寫一下propTypes約束AutoComplete.propTypes = { value: PropTypes.string.isRequired, options: PropTypes.array.isRequired, onValueChange: PropTypes.func.isRequired};export default AutoComplete;

為了方便調試,把BookEditor里的owner_id輸入框換成AutoComplete,傳入一些測試數據:

...import AutoComplete from './AutoComplete';class BookEditor extends React.Component { ... render () { const {form: {name, price, owner_id}, onFormChange} = this.props; return ( <form onSubmit={this.handleSubmit}> ... <FormItem label="所有者:" valid={owner_id.valid} error={owner_id.error}> <AutoComplete value={owner_id.value ? owner_id.value + '' : ''} options={['10000(一韜)', '10001(張三)']} onValueChange={value => onFormChange('owner_id', value)} /> </FormItem> </form> ); }}...

現在大概是這個樣子:

有點怪,我們來給它加上樣式。

新建/src/styles文件夾和auto-complete.less文件,寫入代碼:

.wrapper { display: inline-block; position: relative;}.options { margin: 0; padding: 0; list-style: none; top: 110%; left: 0; right: 0; position: absolute; box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, .6); > li { padding: 3px 6px; &.active { background-color: #0094ff; color: white; } }}

給AutoComplete加上className:

import React, { PropTypes } from 'react';import style from '../styles/auto-complete.less';class AutoComplete extends React.Component { ... render () { const {displayValue, activeItemIndex} = this.state; const {value, options} = this.props; return ( <div className={style.wrapper}> <input value={value}/> {options.length > 0 && ( <ul className={style.options}> { options.map((item, index) => { return ( <li key={index} className={activeItemIndex === index ? style.active : ''}> {item.text || item} </li> ); }) } </ul> )} </div> ); }}

稍微順眼一些了吧:

現在需要在AutoComplete中監聽一些事件:

輸入框的onChange輸入框的onKeyDown,用于對上下鍵、回車鍵進行監聽處理列表項目的onClick列表項目的onMouseEnter,用于在鼠標移入時設置activeItemIndex列表的onMouseLeave,用戶鼠標移出時重置activeItemIndex...function getItemValue (item) { return item.value || item;}class AutoComplete extends React.Component { constructor (props) { ... this.handleKeyDown = this.handleKeyDown.bind(this); this.handleLeave = this.handleLeave.bind(this); } ... handleChange (value) { } handleKeyDown (e) { } handleEnter (index) { } handleLeave () { } render () { const {displayValue, activeItemIndex} = this.state; const {value, options} = this.props; return ( <div className={style.wrapper}> <input value={value} onChange={e => this.handleChange(e.target.value)} onKeyDown={this.handleKeyDown} /> {options.length > 0 && ( <ul className={style.options} onMouseLeave={this.handleLeave}> { options.map((item, index) => { return ( <li key={index} className={index === activeItemIndex ? style.active : ''} onMouseEnter={() => this.handleEnter(index)} onClick={() => this.handleChange(getItemValue(item))} > {item.text || item} </li> ); }) } </ul> )} </div> ); }}...

先來實現handleChange方法,handleChange方法用于在用戶輸入、選擇列表項的時候重置內部狀態(清空displayName、設置activeItemIndex為-1),并通過回調將新的值傳遞給組件使用者:

... handleChange (value) { this.setState({activeItemIndex: -1, displayValue: ''}); this.props.onValueChange(value); } ...

然后是handleKeyDown方法,這個方法中需要判斷當前按下的鍵是否為上下方向鍵或回車鍵,如果是上下方向鍵則根據方向設置當前被選中的列表項;如果是回車鍵并且當前有選中狀態的列表項,則調用handleChange:

... handleKeyDown (e) { const {activeItemIndex} = this.state; const {options} = this.props; switch (e.keyCode) { // 13為回車鍵的鍵碼(keyCode) case 13: { // 判斷是否有列表項處于選中狀態 if (activeItemIndex >= 0) { // 防止按下回車鍵后自動提交表單 e.preventDefault(); e.stopPropagation(); this.handleChange(getItemValue(options[activeItemIndex])); } break; } // 38為上方向鍵,40為下方向鍵 case 38: case 40: { e.preventDefault(); // 使用moveItem方法對更新或取消選中項 this.moveItem(e.keyCode === 38 ? 'up' : 'down'); break; } } } moveItem (direction) { const {activeItemIndex} = this.state; const {options} = this.props; const lastIndex = options.length - 1; let newIndex = -1; // 計算新的activeItemIndex if (direction === 'up') { if (activeItemIndex === -1) { // 如果沒有選中項則選擇最后一項 newIndex = lastIndex; } else { newIndex = activeItemIndex - 1; } } else { if (activeItemIndex < lastIndex) { newIndex = activeItemIndex + 1; } } // 獲取新的displayValue let newDisplayValue = ''; if (newIndex >= 0) { newDisplayValue = getItemValue(options[newIndex]); } // 更新狀態 this.setState({ displayValue: newDisplayValue, activeItemIndex: newIndex }); } ...

handleEnter和handleLeave方法比較簡單:

... handleEnter (index) { const currentItem = this.props.options[index]; this.setState({activeItemIndex: index, displayValue: getItemValue(currentItem)}); } handleLeave () { this.setState({activeItemIndex: -1, displayValue: ''}); } ...

看一下效果:

基本上已經實現了自動完成組件,但是從圖中可以發現選擇后的值把用戶名也帶上了。

但是如果吧options中的用戶名去掉,這個自動完成也就沒有什么意義了,我們來把BookEditor中傳入的options改一改:

... <AutoComplete value={owner_id.value ? owner_id.value + '' : ''} options={[{text: '10000(一韜)', value: 10000}, {text: '10001(張三)', value: 10001}]} onValueChange={value => onFormChange('owner_id', value)} /> ...

刷新看一看,已經達到了我們期望的效果:

有時候我們顯示的值并不一定是我們想要得到的值,這也是為什么我在組件的代碼里有一個getItemValue方法了。

調用接口獲取建議列表

也許有人要問了,這個建議列表為什么一直存在?

這是因為我們為了方便測試給了一個固定的options值,現在來完善一下,修改BookEditor.js:

import React from 'react';import FormItem from './FormItem';import AutoComplete from './AutoComplete';import formProvider from '../utils/formProvider';class BookEditor extends React.Component { constructor (props) { super(props); this.state = { recommendUsers: [] }; ... } ... getRecommendUsers (partialUserId) { fetch('http://localhost:3000/user?id_like=' + partialUserId) .then((res) => res.json()) .then((res) => { if (res.length === 1 && res[0].id === partialUserId) { // 如果結果只有1條且id與輸入的id一致,說明輸入的id已經完整了,沒必要再設置建議列表 return; } // 設置建議列表 this.setState({ recommendUsers: res.map((user) => { return { text: `${user.id}(${user.name})`, value: user.id }; }) }); }); } timer = 0; handleOwnerIdChange (value) { this.props.onFormChange('owner_id', value); this.setState({recommendUsers: []}); // 使用“節流”的方式進行請求,防止用戶輸入的過程中過多地發送請求 if (this.timer) { clearTimeout(this.timer); } if (value) { // 200毫秒內只會發送1次請求 this.timer = setTimeout(() => { // 真正的請求方法 this.getRecommendUsers(value); this.timer = 0; }, 200); } } render () { const {recommendUsers} = this.state; const {form: {name, price, owner_id}, onFormChange} = this.props; return ( <form onSubmit={this.handleSubmit}> ... <FormItem label="所有者:" valid={owner_id.valid} error={owner_id.error}> <AutoComplete value={owner_id.value ? owner_id.value + '' : ''} options={recommendUsers} onValueChange={value => this.handleOwnerIdChange(value)} /> </FormItem> ... </form> ); }}...

看一下最后的樣子:


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
懂色av中文一区二区三区天美| 亚洲免费电影在线观看| 欧美老女人性生活| 欧美综合在线观看| 亚洲片国产一区一级在线观看| 亚洲精品成人网| 欧美高清视频一区二区| 亚洲一区二区久久久| 久久全球大尺度高清视频| 人人澡人人澡人人看欧美| 国产91精品网站| 欧美日韩在线另类| 精品久久久久久久大神国产| 精品呦交小u女在线| 91精品综合久久久久久五月天| 免费av在线一区| 一个色综合导航| 97免费中文视频在线观看| 成人美女av在线直播| 亚洲国产精品推荐| 91网在线免费观看| 久久99国产精品久久久久久久久| 国产欧美精品一区二区三区-老狼| 欧美午夜激情小视频| 国产丝袜一区二区三区| 亚洲裸体xxxx| 成人中心免费视频| 亚洲精品国产精品国产自| 91伊人影院在线播放| 亚洲免费影视第一页| 国语自产精品视频在线看一大j8| 日韩激情av在线免费观看| 欧美大片欧美激情性色a∨久久| 庆余年2免费日韩剧观看大牛| 1769国内精品视频在线播放| 欧美日韩国产中字| 成人免费网站在线看| 日韩精品极品视频免费观看| 国产精品久久久久久久久粉嫩av| 日韩免费看的电影电视剧大全| 国产美女被下药99| 91免费人成网站在线观看18| 精品日韩中文字幕| 国产精品午夜国产小视频| 欧美激情女人20p| 成人免费看吃奶视频网站| 欧美黄色成人网| 亚洲a∨日韩av高清在线观看| 亚洲欧美制服丝袜| 91精品国产91久久| 国产精品久久久久久网站| 亚洲精品wwww| 国产成人一区二区| 欧美老妇交乱视频| 久久精品国产96久久久香蕉| 亚洲国产精品va在线看黑人| 欧美成人久久久| 最近日韩中文字幕中文| 国产91精品黑色丝袜高跟鞋| 国产精品激情av在线播放| 亚洲第一色中文字幕| 国产精品白丝jk喷水视频一区| 欧美日韩成人网| 亚洲激情在线观看| 欧美电影在线观看网站| 久久久av电影| 色偷偷av亚洲男人的天堂| 另类图片亚洲另类| 国产精品欧美激情| 亚洲伊人第一页| 91中文字幕一区| 欧洲日韩成人av| 国产精品一区久久久| 91大神福利视频在线| 久久久亚洲成人| 欧美日韩一区二区在线播放| 精品久久香蕉国产线看观看gif| 国产精品亚洲视频在线观看| 亚洲欧美另类自拍| 亚洲aa中文字幕| 国产精品久久久久不卡| 在线日韩av观看| 欧美激情视频网站| 91精品国产91| 热re91久久精品国99热蜜臀| 538国产精品一区二区免费视频| 91精品国产91久久久久| 91系列在线播放| 国产欧美久久一区二区| 97超碰蝌蚪网人人做人人爽| 久久91亚洲精品中文字幕奶水| 丁香五六月婷婷久久激情| 精品久久久久人成| 国产精品电影久久久久电影网| 亚洲国产精品久久久| 国产91精品久久久久| 亚洲电影在线观看| 国产精品高潮呻吟视频| 欧美日韩国产中文字幕| 亚洲成在人线av| 亚洲精品国产精品乱码不99按摩| 久久久91精品| 91久久嫩草影院一区二区| 国产精品va在线播放我和闺蜜| 91在线精品播放| 日韩毛片在线观看| 丰满岳妇乱一区二区三区| 国语自产精品视频在免费| 亚洲色图av在线| 亚洲欧美激情一区| 欧美主播福利视频| 亚洲国产精品女人久久久| 精品香蕉在线观看视频一| 久久亚洲精品一区二区| 日韩av第一页| 欧美精品性视频| 视频在线观看99| 久久露脸国产精品| 亚洲精品自拍视频| 岛国av一区二区三区| 欧美—级高清免费播放| 久久99久久99精品中文字幕| 亚洲少妇中文在线| 美女福利视频一区| 国产一区红桃视频| 亚洲直播在线一区| 亚洲综合在线播放| 亚洲精品欧美日韩| 亚洲xxxx视频| 国产精品视频区1| 色综合91久久精品中文字幕| 日韩欧美大尺度| 亚洲区在线播放| 最近的2019中文字幕免费一页| 欧美日韩在线视频一区二区| 97超级碰碰人国产在线观看| 精品无人区太爽高潮在线播放| 欧美国产视频一区二区| 久久久久久久91| 亚洲自拍另类欧美丝袜| 亚洲成人网久久久| 日本午夜精品理论片a级appf发布| 国产精品免费久久久| 91精品国产高清久久久久久91| 97国产一区二区精品久久呦| 精品香蕉一区二区三区| 久久视频在线看| 国产一区深夜福利| 欧美电影在线免费观看网站| 91啪国产在线| 中文字幕少妇一区二区三区| 九九精品在线播放| 国产中文字幕91| 亚洲第一中文字幕在线观看| 伊人久久大香线蕉av一区二区| 日韩在线视频国产| 国产精品一区二区性色av| 色在人av网站天堂精品| 亚洲激情自拍图| 亚洲高清久久久久久| 国产日韩在线免费| 1769国产精品| 欧美性受xxxx白人性爽| 91九色国产视频|