麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 網站 > 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在线 | 欧美成人免费在线视频 | 久久久久久久久久综合 | 精品一区二区视频在线观看 | 精品久久久久久久久久久久久 | 国色天香综合网 | 九一成人 | 99欧美精品 | 3344永久免费 | 看一级毛片 | 亚洲精品欧美二区三区中文字幕 | 国产精品一区网站 | 娇喘视频在线观看 | 内地av在线 | 国产免费让你躁在线视频 | 中文字幕在线观看二区 | 性插视频 | 亚洲欧美一区二区三区在线观看 | 久久av一区二区 | av不卡免费在线观看 | 黄色大片在线免费观看 | 国产免费传媒av片在线 | 可以看毛片的网址 | 欧美日韩在线影院 | 亚洲成人国产 | 免费观看国产精品视频 | 欧美片a | 欧美一级特黄aaaaaaa什 | 精品久久久久久久久久久久久久久久久久久 | 精品国产一区二区三区四区阿崩 | 欧美性受xxxxxx黑人xyx性爽 | 爱草成年 | 毛片毛片免费看 | 久久久久久久久久久影视 | 黄色大片在线免费观看 | 婷婷一区二区三区 | 免费h片网站 | 黄色高清免费网站 | 黄色av免费网站 |