房源發布模塊
目標
- 如何解決JS 文本輸入框防抖(用戶輸入過快導致請求服務器的壓力過大)
- 能夠完成搜索模塊
- 能夠獲取發布房源的相關信息
- 能夠知道圖片上傳的流程
- 能夠完成圖片上傳功能
- 能夠完成房源發布功能
前期準備工作
功能
模板改動說明
- 修改首頁(Index)去出租鏈接為: /rent/add
- 修改公共組件NoHouse的children屬性校驗為: node(任何可以渲染的內容)
- 修改公共組件HousePackage,添加onSelect的默認值
- 添加utils/city.js,封裝當前定位城市 localStorage的操作
- 創建了三個頁面組件:Rent(已發布房源列表)、Rent/Add(發布房源)、Rent/Search(關鍵詞搜索校區信息)
import React, { Component } from 'react'import { Link } from 'react-router-dom'import { API, BASE_URL } from '../../utils'import NavHeader from '../../components/NavHeader'
import HouseItem from '../../components/HouseItem'
import NoHouse from '../../components/NoHouse'import styles from './index.module.css'export default class Rent extends Component {state = {// 出租房屋列表list: []}// 獲取已發布房源的列表數據async getHouseList() {const res = await API.get('/user/houses')const { status, body } = res.dataif (status === 200) {this.setState({list: body})} else {const { history, location } = this.propshistory.replace('/login', {from: location})}}componentDidMount() {this.getHouseList()}renderHouseItem() {const { list } = this.stateconst { history } = this.propsreturn list.map(item => {return (<HouseItemkey={item.houseCode}onClick={() => history.push(`/detail/${item.houseCode}`)}src={BASE_URL + item.houseImg}title={item.title}desc={item.desc}tags={item.tags}price={item.price}/>)})}renderRentList() {const { list } = this.stateconst hasHouses = list.length > 0if (!hasHouses) {return (<NoHouse>您還沒有房源,<Link to="/rent/add" className={styles.link}>去發布房源</Link>吧~</NoHouse>)}return <div className={styles.houses}>{this.renderHouseItem()}</div>}render() {const { history } = this.propsreturn (<div className={styles.root}><NavHeader onLeftClick={() => history.go(-1)}>房屋管理</NavHeader>{this.renderRentList()}</div>)}
}
三個路由規則配置
- 在App.js 中導入Rent已發布房源列表頁面
- 在App.js 中導入AuthRoute組件
- 使用AuthRoute組件,配置路由規則
- 使用同樣方式,配置Rent/Add 房源發布頁面,Rent/Search 關鍵詞搜索小區信息頁面
{/* 配置登錄后,才能訪問的頁面 */}
<AuthRoute exact path="/rent" component={Rent} />
<AuthRoute path="/rent/add" component={RentAdd} />
<AuthRoute path="/rent/search" component={RentSearch} />
搜索模塊(★★★)
關鍵詞搜索小區信息
- 獲取SearchBar 搜索欄組件的值
- 在搜索欄的change事件中,判斷當前值是否為空
- 如果為空,直接return,不做任何處理
- 如果不為空,就根據當前輸入的值以及當前城市id,獲取該關鍵詞對應的小區信息
- **問題:**搜索欄中每輸入一個值,就發一次請求,這樣對服務器壓力比較大,用戶體驗不好
- **解決方式:**使用定時器來進行延遲執行(關鍵詞:JS文本框輸入 防抖)
實現步驟
- 給SearchBar組件,添加onChange配置項,獲取文本框的值
<div className={styles.root}>{/* 搜索框 */}<SearchBarplaceholder="請輸入小區或地址"value={searchTxt}onChange={this.handleSearchTxt}showCancelButton={true}onCancel={() => history.go(-1)}/>{/* 搜索提示列表 */}<ul className={styles.tips}>{this.renderTips()}</ul>
</div>
- 判斷當前文本框的值是否為空
- 如果為空,清空列表,然后return,不再發送請求
handleSearchTxt = value => {this.setState({ searchTxt: value })if (!value) {// 文本框的值為空return this.setState({tipsList: []})}}
- 如果不為空,使用API發送請求,獲取小區數據
- 使用定時器來延遲搜索,提升性能
handleSearchTxt = value => {this.setState({ searchTxt: value })if (!value) {// 文本框的值為空return this.setState({tipsList: []})}// 清除上一次的定時器clearTimeout(this.timerId)this.timerId = setTimeout(async () => {// 獲取小區數據const res = await API.get('/area/community', {params: {name: value,id: this.cityId}})this.setState({tipsList: res.data.body})}, 500)}
傳遞校區信息給發布房源頁面
// 渲染搜索結果列表renderTips = () => {const { tipsList } = this.statereturn tipsList.map(item => (<likey={item.community}className={styles.tip}onClick={() => this.onTipsClick(item)}>{item.communityName}</li>))}
- 在事件處理程序中,調用 history.replace() 方法跳轉到發布房源頁面
- 將被點擊的校區信息作為數據一起傳遞過去
onTipsClick = item => {this.props.history.replace('/rent/add', {name: item.communityName,id: item.community})}
- 在發布房源頁面,判斷history.location.state 是否為空
- 如果為空,不做任何處理
- 如果不為空,則將小區信息存儲到發布房源頁面的狀態中
constructor(props) {super(props)// console.log(props)const { state } = props.locationconst community = {name: '',id: ''}if (state) {// 有小區信息數據,存儲到狀態中community.name = state.namecommunity.id = state.id}
}
發布房源
布局結構
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OZjtHyBI-1575112741233)(images/發布房源 -布局結構.png)]
- List列表 組件
- InputItem 文本輸入組件
- TextareaItem 多行輸入組件
- Picker 選擇器組件
- ImagePicker 圖片選擇器組件
- 模板結構
import React, { Component } from 'react'import {Flex,List,InputItem,Picker,ImagePicker,TextareaItem,Modal
} from 'antd-mobile'import NavHeader from '../../../components/NavHeader'
import HousePackge from '../../../components/HousePackage'import styles from './index.module.css'const alert = Modal.alert// 房屋類型
const roomTypeData = [{ label: '一室', value: 'ROOM|d4a692e4-a177-37fd' },{ label: '二室', value: 'ROOM|d1a00384-5801-d5cd' },{ label: '三室', value: 'ROOM|20903ae0-c7bc-f2e2' },{ label: '四室', value: 'ROOM|ce2a5daa-811d-2f49' },{ label: '四室+', value: 'ROOM|2731c38c-5b19-ff7f' }
]// 朝向:
const orientedData = [{ label: '東', value: 'ORIEN|141b98bf-1ad0-11e3' },{ label: '西', value: 'ORIEN|103fb3aa-e8b4-de0e' },{ label: '南', value: 'ORIEN|61e99445-e95e-7f37' },{ label: '北', value: 'ORIEN|caa6f80b-b764-c2df' },{ label: '東南', value: 'ORIEN|dfb1b36b-e0d1-0977' },{ label: '東北', value: 'ORIEN|67ac2205-7e0f-c057' },{ label: '西南', value: 'ORIEN|2354e89e-3918-9cef' },{ label: '西北', value: 'ORIEN|80795f1a-e32f-feb9' }
]// 樓層
const floorData = [{ label: '高樓層', value: 'FLOOR|1' },{ label: '中樓層', value: 'FLOOR|2' },{ label: '低樓層', value: 'FLOOR|3' }
]export default class RentAdd extends Component {constructor(props) {super(props)// console.log(props)const { state } = props.locationconst community = {name: '',id: ''}if (state) {// 有小區信息數據,存儲到狀態中community.name = state.namecommunity.id = state.id}this.state = {// 臨時圖片地址tempSlides: [],// 小區的名稱和idcommunity,// 價格price: '',// 面積size: '',// 房屋類型roomType: '',// 樓層floor: '',// 朝向:oriented: '',// 房屋標題title: '',// 房屋圖片houseImg: '',// 房屋配套:supporting: '',// 房屋描述description: ''}}render() {const Item = List.Itemconst { history } = this.propsconst {community,price,roomType,floor,oriented,description,tempSlides,title,size} = this.statereturn (<div className={styles.root}><NavHeader onLeftClick={this.onCancel}>發布房源</NavHeader>{/* 房源信息 */}<ListclassName={styles.header}renderHeader={() => '房源信息'}data-role="rent-list">{/* 選擇所在小區 */}<Itemextra={community.name || '請輸入小區名稱'}arrow="horizontal"onClick={() => history.replace('/rent/search')}>小區名稱</Item>{/* 相當于 form 表單的 input 元素 */}<InputItemplaceholder="請輸入租金/月"extra="¥/月"value={price}>租 金</InputItem><InputItemplaceholder="請輸入建筑面積"extra="㎡"value={size}onChange={val => this.getValue('size', val)}>建筑面積</InputItem><Pickerdata={roomTypeData}value={[roomType]}cols={1}><Item arrow="horizontal">戶 型</Item></Picker><Pickerdata={floorData}value={[floor]}cols={1}><Item arrow="horizontal">所在樓層</Item></Picker><Pickerdata={orientedData}value={[oriented]}cols={1}><Item arrow="horizontal">朝 向</Item></Picker></List>{/* 房屋標題 */}<ListclassName={styles.title}renderHeader={() => '房屋標題'}data-role="rent-list"><InputItemplaceholder="請輸入標題(例如:整租 小區名 2室 5000元)"value={title}/></List>{/* 房屋圖像 */}<ListclassName={styles.pics}renderHeader={() => '房屋圖像'}data-role="rent-list"><ImagePickerfiles={tempSlides}multiple={true}className={styles.imgpicker}/></List>{/* 房屋配置 */}<ListclassName={styles.supporting}renderHeader={() => '房屋配置'}data-role="rent-list"><HousePackge select /></List>{/* 房屋描述 */}<ListclassName={styles.desc}renderHeader={() => '房屋描述'}data-role="rent-list"><TextareaItemrows={5}placeholder="請輸入房屋描述信息"value={description}/></List><Flex className={styles.bottom}><Flex.Item className={styles.cancel} onClick={this.onCancel}>取消</Flex.Item><Flex.Item className={styles.confirm} onClick={this.addHouse}>提交</Flex.Item></Flex></div>)}
}
獲取房源數據分析(★★)
- InputItem、TextareaItem、Picker組件,都使用onChange配置項,來獲取當前值
- 處理方式:封裝一個事件處理函數 getValue 來統一獲取三種組件的值
- 創建方法getValue作為三個組件的事件處理函數
- 該方法接受兩個參數:1. name 當前狀態名;2. value 當前輸入值或者選中值
- 分別給 InputItem/TextareaItem/Picker 組件,添加onChange配置項
- 分別調用 getValue 并傳遞 name 和 value 兩個參數(注意:Picker組件選中值為數組,而接口需要字符串,所以,取索引號為 0 的值即可)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ICGMW4Z9-1575112741237)(images/getValue.png)]
示例代碼:
/* 獲取表單數據:*/getValue = (name, value) => {this.setState({[name]: value})}// 給相應組件添加 onChange 事件,傳遞 name 和value
獲取房屋配置數據(★★)
- 給HousePackge 組件, 添加 onSelect 屬性
- 在onSelect 處理方法中,通過參數獲取到當前選中項的值
- 根據發布房源接口的參數說明,將獲取到的數組類型的選中值,轉化為字符串類型
- 調用setState 更新狀態
/* 獲取房屋配置數據
*/
handleSupporting = selected => {this.setState({supporting: selected.join('|')})
}...
<HousePackge select onSelect={this.handleSupporting} />
圖片上傳(★★★)
分析
- 根據發布房源接口,最終需要的是房屋圖片的路徑
- 兩個步驟: 1- 獲取房屋圖片; 2- 上傳圖片獲取到圖片的路徑
- 如何獲取房屋圖片? ImagePicker圖片選擇器組件,通過onChange配置項來獲取
- 如何上傳房屋圖片? 根據圖片上傳接口,將圖片轉化為FormData數據后再上傳,由接口返回圖片路徑
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-clwi2iUG-1575112741238)(images/圖片上傳接口.png)]
獲取房屋圖片
要上傳圖片,首先需要先獲取到房屋圖片
- 給ImagePicker 組件添加 onChange 配置項
- 通過onChange 的參數,獲取到上傳的圖片,并且存儲到tempSlides中
handleHouseImg = (files, type, index) => {// files 圖片文件的數組; type 操作類型:添加,移除(如果是移除,那么第三個參數代表就是移除的圖片的索引)console.log(files, type, index)this.setState({tempSlides: files})}...<ImagePickerfiles={tempSlides}onChange={this.handleHouseImg}multiple={true}className={styles.imgpicker}/>
上傳房屋圖片
圖片已經可以通過 ImagePicker 的 onChange 事件來獲取到了,接下來就需要把圖片進行上傳,然后獲取到服務器返回的成功上傳圖片的路徑
- 給提交按鈕,綁定點擊事件
- 在事件處理函數中,判斷是否有房屋圖片
- 如果沒有,不做任何處理
- 如果有,就創建FormData的示例對象(form)
- 遍歷tempSlides數組,分別將每一個圖片圖片對象,添加到form中(鍵為:file,根據接口文檔獲取)
- 調用圖片上傳接口,傳遞form參數,并設置請求頭 Content-Type 為 multipart/form-data
- 通過接口返回值獲取到圖片路徑
// 上傳圖片
addHouse = async() => {const { tempSlides } = this.statelet houseImg = ''if (tempSlides.length > 0) {// 已經有上傳的圖片了const form = new FormData()tempSlides.forEach(item => form.append('file', item.file))const res = await API.post('/houses/image', form, {headers: {'Content-Type': 'multipart/form-data'}})// console.log(res)houseImg = res.data.body.join('|')}
}
...
<Flex.Item className={styles.confirm} onClick={this.addHouse}>提交
</Flex.Item>
發布房源
到現在,我們已經可以獲取到發布房源的所有信息了,接下來就需要把數據傳遞給服務器
- 在 addHouse 方法中, 從state 里面獲取到所有的房屋數據
- 使用API 調用發布房源接口,傳遞所有房屋數據
- 根據接口返回值中的狀態碼,判斷是否發布成功
- 如果狀態碼是200,標示發布成功,就提示:發布成功,并跳轉到已發布的房源頁面
- 否則,就提示:服務器偷懶了,請稍后再試
addHouse = async () => {const {tempSlides,title,description,oriented,supporting,price,roomType,size,floor,community} = this.statelet houseImg = ''// 上傳房屋圖片:if (tempSlides.length > 0) {// 已經有上傳的圖片了const form = new FormData()tempSlides.forEach(item => form.append('file', item.file))const res = await API.post('/houses/image', form, {headers: {'Content-Type': 'multipart/form-data'}})houseImg = res.data.body.join('|')}// 發布房源const res = await API.post('/user/houses', {title,description,oriented,supporting,price,roomType,size,floor,community: community.id,houseImg})if (res.data.status === 200) {// 發布成功Toast.info('發布成功', 1, null, false)this.props.history.push('/rent')} else {Toast.info('服務器偷懶了,請稍后再試~', 2, null, false)}
}
項目打包
目標
- 能夠配置生產環境的環境變量
- 能夠完成簡易的打包
- 知道react中如果要配置webpack的兩種方式
- 知道 antd-mobile 按需加載的好處
- 知道路由代碼分割的好處
- 能夠參照筆記來進行 按需加載配置和代碼分割配置,然后打包
- 能夠知道如何解決react中跨域問題
簡易打包(★★★)
- 打開 create-react-app 腳手架的 打包文檔說明
- 在根目錄創建 .env.production 文件,配置生產環境的接口基礎路徑
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dCBhVvUr-1575112741243)(images/生產環境.jpg)]
- 在項目根目錄中,打開終端
- 輸入命令: yarn build,進行項目打包,生成build文件夾(打包好的項目內容)
- 將build目錄中的文件內容,部署到都服務器中即可
- 也可以通過終端中的提示,使用 serve-s build 來本地查看(需要全局安裝工具包 serve)
如果出現以下提示,就代表打包成功,在根目錄中就會生成一個build文件夾
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8arTTF5q-1575112741247)(images/build命令.png)]
腳手架的配置說明(★★★)
antd-mobile 按需加載(★★★)
- 打開 antd-mobile 在create-react-app中的使用文檔
- 安裝 yarn add react-app-rewired customize-cra(用于腳手架重寫配置)
- 修改package.json 中的 scripts
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8WNqUAqi-1575112741249)(images/scripts配置.png)]
- 在項目根目錄創建文件: config-overrides.js(用于覆蓋腳手架默認配置)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NfRi4YbM-1575112741254)(images/overrides.png)]
- 安裝 yarn add babel-plugin-import 插件(用于按需加載組件代碼和樣式)
- 修改 config-overrides.js 文件,配置按需加載功能
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(fixBabelImports('import', {libraryName: 'antd-mobile',style: 'css',}),
);
- 重啟項目(yarn start)
- 移除index.js 中導入的 antd-mobile樣式文件
- 將index.css 移動到App后面,讓index.css 中的頁面背景生效
打完包后,你會發現,兩次打包的體積會有變化,這樣達到了一個代碼體積優化的層面
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-U21VZObR-1575112741257)(images/兩次打包對比.png)]
基于路由代碼分割(★★★)
- 目的:將代碼按照路由進行分割,只在訪問該路由的時候才加載該組件內容,提高首屏加載速度
- 如何實現? React.lazy() 方法 + import() 方法、Suspense組件(React Code-Splitting文檔)
- React.lazy() 作用: 處理動態導入的組件,讓其像普通組件一樣使用
- import(‘組件路徑’),作用:告訴webpack,這是一個代碼分割點,進行代碼分割
- Suspense組件:用來在動態組件加載完成之前,顯示一些loading內容,需要包裹動態組件內容
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nR1oGuPt-1575112741259)(images/路由分割.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3qAur6qu-1575112741264)(images/suspense.png)]
項目中代碼修改:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DtzZ8ATO-1575112741269)(images/項目代碼.png)]
其他性能優化(★★)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zqucj2vu-1575112741271)(images/長列表優化.png)]
好客租房移動Web(下)-總結
- 登錄模塊:使用Fomik組件實現了表單處理和表單校驗、封裝鑒權路由AuthRoute和axios攔截器實現登錄訪問控制
- 我的收藏模塊:添加、取消收藏
- 發布房源模塊:小區關鍵詞搜索、圖片上傳、發布房源信息
- 項目打包和優化:antd-mobile組件庫按需加載,基于路由的代碼分割實現組件的按需加載,提高了首屏加載速度
總結
以上是生活随笔為你收集整理的react好租客项目Day11-发布房源模块(js输入框防抖图片上传)项目打包项目优化(按需加载路由代码分割)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。