src / pages / BookAdd.js // 圖書添加頁javascript
/** * 圖書添加頁面 */ import React from 'react'; // 佈局組件 import HomeLayout from '../layouts/HomeLayout'; // 編輯組件 import BookEditor from '../components/BookEditor'; class BookAdd extends React.Component { render() { return ( <HomeLayout title="添加圖書"> <BookEditor /> </HomeLayout> ); } } export default BookAdd;
src / pages / BookList.js // 圖書列表頁css
/** * 圖書列表頁面 */ import React from 'react'; // 佈局組件 import HomeLayout from '../layouts/HomeLayout'; // 引入 prop-types import PropTypes from 'prop-types'; class BookList extends React.Component { // 構造器 constructor(props) { super(props); // 定義初始化狀態 this.state = { bookList: [] }; } /** * 生命週期 * componentWillMount * 組件初始化時只調用,之後組件更新不調用,整個生命週期只調用一次 */ componentWillMount(){ // 請求數據 fetch('http://localhost:8000/book') .then(res => res.json()) .then(res => { /** * 成功的回調 * 數據賦值 */ this.setState({ bookList: res }); }); } /** * 編輯 */ handleEdit(book){ // 跳轉編輯頁面 this.context.router.push('/book/edit/' + book.id); } /** * 刪除 */ handleDel(book){ // 確認框 const confirmed = window.confirm(`確認要刪除書名 ${book.name} 嗎?`); // 判斷 if(confirmed){ // 執行刪除數據操做 fetch('http://localhost:8000/book/' + book.id, { method: 'delete' }) .then(res => res.json()) .then(res => { /** * 設置狀態 * array.filter * 把Array的某些元素過濾掉,而後返回剩下的元素 */ this.setState({ bookList: this.state.bookList.filter(item => item.id !== book.id) }); alert('刪除用戶成功'); }) .catch(err => { console.log(err); alert('刪除用戶失敗'); }); } } render() { // 定義變量 const { bookList } = this.state; return ( <HomeLayout title="圖書列表"> <table> <thead> <tr> <th>圖書ID</th> <th>圖書名稱</th> <th>價格</th> <th>操做</th> </tr> </thead> <tbody> { bookList.map((book) => { return ( <tr key={book.id}> <td>{book.id}</td> <td>{book.name}</td> <td>{book.price}</td> <td> <a onClick={() => this.handleEdit(book)}>編輯</a> <a onClick={() => this.handleDel(book)}>刪除</a> </td> </tr> ); }) } </tbody> </table> </HomeLayout> ); } } /** * 任何使用this.context.xxx的地方,必須在組件的contextTypes裏定義對應的PropTypes */ BookList.contextTypes = { router: PropTypes.object.isRequired }; export default BookList;
src / components / BookEditor.js // 圖書編輯組件java
/** * 圖書編輯器組件 */ import React from 'react'; import FormItem from '../components/FormItem'; // 或寫成 ./FormItem // 高階組件 formProvider表單驗證 import formProvider from '../utils/formProvider'; // 引入 prop-types import PropTypes from 'prop-types'; class BookEditor extends React.Component { // 按鈕提交事件 handleSubmit(e){ // 阻止表單submit事件自動跳轉頁面的動做 e.preventDefault(); // 定義常量 const { form: { name, price, owner_id }, formValid, editTarget} = this.props; // 組件傳值 // 驗證 if(!formValid){ alert('請填寫正確的信息後重試'); return; } // 默認值 let editType = '添加'; let apiUrl = 'http://localhost:8000/book'; let method = 'post'; // 判斷類型 if(editTarget){ editType = '編輯'; apiUrl += '/' + editTarget.id; method = 'put'; } // 發送請求 fetch(apiUrl, { method, // method: method 的簡寫 // 使用fetch提交的json數據須要使用JSON.stringify轉換爲字符串 body: JSON.stringify({ name: name.value, price: price.value, owner_id: owner_id.value }), headers: { 'Content-Type': 'application/json' } }) // 強制回調的數據格式爲json .then((res) => res.json()) // 成功的回調 .then((res) => { // 當添加成功時,返回的json對象中應包含一個有效的id字段 // 因此可使用res.id來判斷添加是否成功 if(res.id){ alert(editType + '添加圖書成功!'); this.context.router.push('/book/list'); // 跳轉到用戶列表頁面 return; }else{ alert(editType + '添加圖書失敗!'); } }) // 失敗的回調 .catch((err) => console.error(err)); } // 生命週期--組件加載中 componentWillMount(){ const {editTarget, setFormValues} = this.props; if(editTarget){ setFormValues(editTarget); } } render() { // 定義常量 const {form: {name, price, owner_id}, onFormChange} = this.props; return ( <form onSubmit={(e) => this.handleSubmit(e)}> <FormItem label="書名:" valid={name.valid} error={name.error}> <input type="text" value={name.value} onChange={(e) => onFormChange('name', e.target.value)}/> </FormItem> <FormItem label="價格:" valid={price.valid} error={price.error}> <input type="number" value={price.value || ''} onChange={(e) => onFormChange('price', e.target.value)}/> </FormItem> <FormItem label="全部者:" valid={owner_id.valid} error={owner_id.error}> <input type="text" value={owner_id.value || ''} onChange={(e) => onFormChange('owner_id', e.target.value)}/> </FormItem> <br /> <input type="submit" value="提交" /> </form> ); } } // 必須給BookEditor定義一個包含router屬性的contextTypes // 使得組件中能夠經過this.context.router來使用React Router提供的方法 BookEditor.contextTypes = { router: PropTypes.object.isRequired }; // 實例化 BookEditor = formProvider({ // field 對象 // 書名 name: { defaultValue: '', rules: [ { pattern: function (value) { return value.length > 0; }, error: '請輸入圖書戶名' }, { pattern: /^.{1,10}$/, error: '圖書名最多10個字符' } ] }, // 價格 price: { defaultValue: 0, rules: [ { pattern: function(value){ return value > 0; }, error: '價格必須大於0' } ] }, // 全部者 owner_id: { defaultValue: '', rules: [ { pattern: function (value) { return value > 0; }, error: '請輸入全部者名稱' }, { pattern: /^.{1,10}$/, error: '全部者名稱最多10個字符' } ] } })(BookEditor); export default BookEditor;
src / pages / BookEdit.js // 圖書編輯頁react
/** * 編輯圖書頁面 */ import React from 'react'; // 佈局組件 import HomeLayout from '../layouts/HomeLayout'; // 引入 prop-types import PropTypes from 'prop-types'; // 圖書編輯器組件 import BookEditor from '../components/BookEditor'; class BookEdit extends React.Component { // 構造器 constructor(props) { super(props); // 定義初始化狀態 this.state = { book: null }; } // 生命週期--組件加載中 componentWillMount(){ // 定義常量 const bookId = this.context.router.params.id; /** * 發送請求 * 獲取用戶數據 */ fetch('http://localhost:8000/book/' + bookId) .then(res => res.json()) .then(res => { this.setState({ book: res }); }) } render() { const {book} = this.state; return ( <HomeLayout title="編輯圖書"> { book ? <BookEditor editTarget={book} /> : '加載中...' } </HomeLayout> ); } } BookEdit.contextTypes = { router: PropTypes.object.isRequired }; export default BookEdit;
項目結構:json
找了個例子看一下效果:api
能夠發現,這是一個包含一個輸入框、一個下拉框的複合控件。數組
實現一個通用組件,在動手寫代碼以前我會作如下準備工做:app
上面提了,這個組件由一個輸入框和一個下拉框組成。less
注意,這裏的下拉框是一個「僞」下拉框,並非指select與option。仔細看上面的動圖,能夠看得出來這個「僞」下拉框只是一個帶邊框的、位於輸入框正下方的一個列表。編輯器
咱們能夠假設組件的結構是這樣的:
<div> <input type="text"/> <ul> <li>...</li> ... </ul> </div>
觀察動圖,能夠發現組件有如下行爲:
一個易用的通用組件應該對外隱藏只有內部使用的狀態。使用React組件的state來維護組件的內部狀態。
根據組件邏輯,咱們能夠肯定自動完成組件須要這些內部狀態:
咱們的目標是一個通用的組件,因此相似組件實際的值、推薦列表這樣的狀態,應該由組件的使用者來控制:
如上圖,組件應向外暴露的屬性有:
肯定了組件結構、組件邏輯、內部狀態和外部屬性以後,就能夠着手進行編碼了:
在/src/components
下新建AutoComplete.js文件,寫入組件的基本代碼:
/** * 自動完成組件 */ import React from 'react'; // 引入 prop-types import PropTypes from 'prop-types'; 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.js加上className:
/** * 自動完成組件 */ import React from 'react'; // 引入 prop-types import PropTypes from 'prop-types'; // 引入樣式 import '../styles/auto-complete.less'; 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 className="wrapper"> <input value={displayValue || value}/> {options.length > 0 && ( <ul className="options"> { options.map((item, index) => { return ( <li key={index} className={activeItemIndex === index ? 'active' : ''}> {item.text || item} </li> ); }) } </ul> )} </div> ); } } // 通用組件最好寫一下propTypes約束 AutoComplete.propTypes = { value: PropTypes.string.isRequired, // 字符串 options: PropTypes.array.isRequired, // 數組 onValueChange: PropTypes.func.isRequired // 函數 }; // 向外暴露 export default AutoComplete;
稍微順眼一些了吧:
如今須要在AutoComplete中監聽一些事件:
... // 得到當前元素value值 function getItemValue (item) { return item.value || item; } class AutoComplete extends React.Component { // 構造器 constructor(props) { super(props); // 定義初始化狀態 this.state = { displayValue: '', activeItemIndex: -1 }; // 對上下鍵、回車鍵進行監聽處理 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="wrapper"> <input value={displayValue || value} onChange={e => this.handleChange(e.target.value)} onKeyDown={this.handleKeyDown} /> {options.length > 0 && ( <ul className="options" onMouseLeave={this.handleLeave}> { options.map((item, index) => { return ( <li key={index} className={activeItemIndex === index ? '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: ''}); } ...
看一下效果:
完整的代碼:
src / components / AutoComplete.js
/** * 自動完成組件 */ import React from 'react'; // 引入 prop-types import PropTypes from 'prop-types'; // 引入樣式 import '../styles/auto-complete.less'; // 得到當前元素value值 function getItemValue (item) { return item.value || item; } class AutoComplete extends React.Component { // 構造器 constructor(props) { super(props); // 定義初始化狀態 this.state = { displayValue: '', activeItemIndex: -1 }; // 對上下鍵、回車鍵進行監聽處理 this.handleKeyDown = this.handleKeyDown.bind(this); // 對鼠標移出進行監聽處理 this.handleLeave = this.handleLeave.bind(this); } // 處理輸入框改變事件 handleChange(value){ // 選擇列表項的時候重置內部狀態 this.setState({ activeItemIndex: -1, displayValue: '' }); // 經過回調將新的值傳遞給組件使用者 this.props.onValueChange(value); } // 處理上下鍵、回車鍵點擊事件 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; } default: { // } } } // 使用moveItem方法對更新或取消選中項 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(index){ const currentItem = this.props.options[index]; this.setState({ activeItemIndex: index, displayValue: getItemValue(currentItem) }); } // 處理鼠標移出事件 handleLeave(){ this.setState({ activeItemIndex: -1, displayValue: '' }); } // 渲染 render() { const {displayValue, activeItemIndex} = this.state; // 組件傳值 const {value, options} = this.props; return ( <div className="wrapper"> <input value={displayValue || value} onChange={e => this.handleChange(e.target.value)} onKeyDown={this.handleKeyDown} /> {options.length > 0 && ( <ul className="options" onMouseLeave={this.handleLeave}> { options.map((item, index) => { return ( <li key={index} className={activeItemIndex === index ? 'active' : ''} onMouseEnter={() => this.handleEnter(index)} onClick={() => this.handleChange(getItemValue(item))} > {item.text || item} </li> ); }) } </ul> )} </div> ); } } // 通用組件最好寫一下propTypes約束 AutoComplete.propTypes = { value: PropTypes.string.isRequired, // 字符串 options: PropTypes.array.isRequired, // 數組 onValueChange: PropTypes.func.isRequired // 函數 }; // 向外暴露 export default AutoComplete;
基本上已經實現了自動完成組件,可是從圖中能夠發現選擇後的值把用戶名也帶上了。
可是若是吧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:8000/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> ); } } ...
看一下最後的樣子:
完整的代碼:
src / components / BookEditor.js
/** * 圖書編輯器組件 */ import React from 'react'; import FormItem from '../components/FormItem'; // 或寫成 ./FormItem // 高階組件 formProvider表單驗證 import formProvider from '../utils/formProvider'; // 引入 prop-types import PropTypes from 'prop-types'; // 引入自動完成組件 import AutoComplete from './AutoComplete'; class BookEditor extends React.Component { // 構造器 constructor(props) { super(props); this.state = { recommendUsers: [] }; } // 獲取推薦用戶信息 getRecommendUsers (partialUserId) { // 請求數據 fetch('http://localhost:8000/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); } } // 按鈕提交事件 handleSubmit(e){ // 阻止表單submit事件自動跳轉頁面的動做 e.preventDefault(); // 定義常量 const { form: { name, price, owner_id }, formValid, editTarget} = this.props; // 組件傳值 // 驗證 if(!formValid){ alert('請填寫正確的信息後重試'); return; } // 默認值 let editType = '添加'; let apiUrl = 'http://localhost:8000/book'; let method = 'post'; // 判斷類型 if(editTarget){ editType = '編輯'; apiUrl += '/' + editTarget.id; method = 'put'; } // 發送請求 fetch(apiUrl, { method, // method: method 的簡寫 // 使用fetch提交的json數據須要使用JSON.stringify轉換爲字符串 body: JSON.stringify({ name: name.value, price: price.value, owner_id: owner_id.value }), headers: { 'Content-Type': 'application/json' } }) // 強制回調的數據格式爲json .then((res) => res.json()) // 成功的回調 .then((res) => { // 當添加成功時,返回的json對象中應包含一個有效的id字段 // 因此可使用res.id來判斷添加是否成功 if(res.id){ alert(editType + '添加圖書成功!'); this.context.router.push('/book/list'); // 跳轉到用戶列表頁面 return; }else{ alert(editType + '添加圖書失敗!'); } }) // 失敗的回調 .catch((err) => console.error(err)); } // 生命週期--組件加載中 componentWillMount(){ const {editTarget, setFormValues} = this.props; if(editTarget){ setFormValues(editTarget); } } render() { // 定義常量 const {recommendUsers} = this.state; const {form: {name, price, owner_id}, onFormChange} = this.props; return ( <form onSubmit={(e) => this.handleSubmit(e)}> <FormItem label="書名:" valid={name.valid} error={name.error}> <input type="text" value={name.value} onChange={(e) => onFormChange('name', e.target.value)}/> </FormItem> <FormItem label="價格:" valid={price.valid} error={price.error}> <input type="number" value={price.value || ''} onChange={(e) => onFormChange('price', e.target.value)}/> </FormItem> <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> <br /> <input type="submit" value="提交" /> </form> ); } } // 必須給BookEditor定義一個包含router屬性的contextTypes // 使得組件中能夠經過this.context.router來使用React Router提供的方法 BookEditor.contextTypes = { router: PropTypes.object.isRequired }; // 實例化 BookEditor = formProvider({ // field 對象 // 書名 name: { defaultValue: '', rules: [ { pattern: function (value) { return value.length > 0; }, error: '請輸入圖書戶名' }, { pattern: /^.{1,10}$/, error: '圖書名最多10個字符' } ] }, // 價格 price: { defaultValue: 0, rules: [ { pattern: function(value){ return value > 0; }, error: '價格必須大於0' } ] }, // 全部者 owner_id: { defaultValue: '', rules: [ { pattern: function (value) { return value > 0; }, error: '請輸入全部者名稱' }, { pattern: /^.{1,10}$/, error: '全部者名稱最多10個字符' } ] } })(BookEditor); export default BookEditor;
.