摘要:
先看效果圖
github地址
初始化項目
#創建項目 create-react-app applist #如果沒有安裝create-react-app的話,先安裝 npm install -g create-react-app
目錄結構改造
|--config |--node_modules |--public |--scripts |--src |-----api //api接口 |-----components //組件 |-----pages //頁面 |-----plugins //插件相關 axios |-----router //路由 |-----store //redux |-----styles //公共樣式 |-----utils //工具包 |-----index.js //入口 |--package.json
Vscode插件安裝
所謂工欲善其事,必先利其器。這里我們推薦一些好用的vscode插件
1. 代碼提示類插件 1.1 Reactjs code snippets 1.2 React Redux ES6 Snippets 1.3 React-Native/React/Redux snippets for es6/es7 1.4 JavaScript (ES6) code snippets(es6代碼片段) 1.5 Typescript React code snippets(這是tsx的react組件片段) 2. 美化類插件 2.1 One Dark Pro(atom風格主題) 2.2 vscode-icons(文件圖標) 3. 其他實用類插件 3.1 Beautify css/sass/scss/less(樣式代碼格式化) 3.2 npm Intellisense(對package.json內中的依賴包的名稱提示) 3.3 Path Intellisense(文件路徑補全) 3.4 cssrem(px轉換為rem) 3.5 CSS Modules(對使用了css modules的jsx標簽的類名補全和跳轉到定義位置) 4.vscode配置設備同步 Settings Sync 有了它就不用每次換個開發環境又重新配置一遍vscode了 5.另外,react的jsx補全html標簽需要在vscode單獨設置一下 首選項-設置-搜索‘includeLanguages’-編輯settings.json添加如下代碼即可 "emmet.includeLanguages": { "javascript": "javascriptreact" }
最后,安裝完插件之后,以下兩個快捷鍵可能會經常使用
rcc 生成有狀態的組件代碼塊
rfc 生成無狀態的組件代碼塊
使用axios插件請求數據並封裝api請求
1、安裝
npm isntall axios --save
2、創建axios.js文件
主要是用來創建axios實例,添加請求攔截,全局處理一些業務邏輯,例如全局loading展示,返回狀態碼處理等 。
具體的配置可查看axios
3、創建api目錄,並新建index.js文件
import axios from '../plugins/axios'; let api = { // app列表 appListData(params){ return axios.get('/mock/appListData.json', params); }, // 推薦 recommendData(params) { return axios.get('/mock/recomendData.json', params); }, // 搜索 lookUp(params) { return axios.get('/mock/lookUp.json', params); } } export default api
4、組件中使用
import $api from '../api/index.js'; $api.recommendData({}).then((response) => { let feed = response.feed; this.setState({ recommendList: feed.entry }) }).catch(err => { console.log(err) })
axios攔截器添加全局loading,多個請求合並一個loading
通過配置axios的過濾器,可以攔截用戶請求,我們在這里添加全局loading,返回時在隱藏loading的顯示。這里有個問題需要解決的是,如果同一時刻我們發起多個請求,那么會出現多個loading的問題,解決辦法就是,通過設定一個count變量來記錄當前接口請求數量,當count為0時再結束loading。
showFullScreenLoading、tryHideFullScreenLoading要干的事兒就是將同一時刻的請求合並,聲明一個變量needLoadingRequestCount,每次調用showFullScreenLoading方法 needLoadingRequestCount + 1。調用tryHideFullScreenLoading()方法,needLoadingRequestCount - 1。needLoadingRequestCount為 0 時,結束 loading。
另外還可以通過參數形式設定不需要顯示loading的請求,在攔截處通過判斷來顯示
1、在common.js文件中添加如下代碼
import { Toast } from 'antd-mobile' /** * 顯示loading */ function showLoading(){ Toast.loading('加載中...', 0); } /** * 隱藏loading */ function hideLoading(){ Toast.hide(); } /** * 合並請求,同一時刻只顯示一個loading */ let needLoadingRequestCount = 0 export function showFullScreenLoading() { if (needLoadingRequestCount === 0) { showLoading() } needLoadingRequestCount++ } export function hideFullScreenLoading() { if (needLoadingRequestCount <= 0){ return } needLoadingRequestCount-- if (needLoadingRequestCount === 0) { hideLoading() } }
2、在axios中使用
import { showFullScreenLoading, hideFullScreenLoading} from '../utils/commons' // Add a request interceptor _axios.interceptors.request.use(function (config) { // Do something before request is sent showFullScreenLoading(); return config; }, function (error) { // Do something with request error return Promise.reject(error); }); // Add a response interceptor _axios.interceptors.response.use(function (response) { // Do something with response data setTimeout(() => { hideFullScreenLoading(); }, 1000); return response.data; }, function (error) { // Do something with response error return Promise.reject(error); });
配置react-router
在React中,常用的有兩個包可以實現這個需求,那就是react-router和react-router-dom,這兩個不同之處就是后者比前者多出了 這樣的 DOM 類組件,所以我們只需要使用react-router-dom就可以了
1、安裝
npm install react-router-dom --save-dev
2、創建路由組件router/index.js
import React from 'react'; import { HashRouter, Route, Switch } from 'react-router-dom'; import Home from '../pages/Home'; import Profile from '../pages/profile/Profile'; const BasicRoute = () => ( <HashRouter> <Switch> <Route exact path="/" component={Home} /> <Route exact path="/profile" component={Profile} /> </Switch> </HashRouter> ); export default BasicRoute;
將兩個頁面組件Home和Detail使用Route組件包裹,外面套用Switch作路由匹配,當路由組件檢測到地址欄與Route的path匹配時,就會自動加載響應的頁面
3、入口文件index.js引入router組件
import React from 'react'; import ReactDOM from 'react-dom'; import Router from './router/router'; ReactDOM.render( <Router/>, document.getElementById('root') );
4、路由跳轉
this.props.history.push("/search/result");
添加vw適配手機屏幕
1、默認webpack的配置是隱藏的,通過eject 顯示webpack配置(此操作不可逆)
npm run eject
2、安裝postcss
npm install --save postcss-aspect-ratio-mini postcss-px-to-viewport postcss-write-svg postcss-cssnext postcss-viewport-units cssnano
3、webpack配置
修改webpack.config.js,添加如下代碼:
{
// Options for PostCSS as we reference these options twice // Adds vendor prefixing based on your specified browser support in // package.json loader: require.resolve('posREtcss-loader'), options: { // Necessary for external CSS imports to work // https://github.com/facebook/create-react-app/issues/2677 ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), require('postcss-preset-env')({ autoprefixer: { flexbox: 'no-2009', }, stage: 3, }), // Adds PostCSS Normalize as the reset css with default options, // so that it honors browserslist config in package.json // which in turn let's users customize the target behavior as per their needs. postcssNormalize(), // 添加vw配置 start postcssAspectRatioMini({}), postcssPxToViewport({ viewportWidth: 750, // (Number) The width of the viewport. viewportHeight: 1334, // (Number) The height of the viewport. unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to. viewportUnit: 'vw', // (String) Expected units. selectorBlackList: ['.ignore', '.hairlines', '.list-row-bottom-line', '.list-row-top-line'], // (Array) The selectors to ignore and leave as px. minPixelValue: 1, // (Number) Set the minimum pixel value to replace. mediaQuery: false // (Boolean) Allow px to be converted in media queries. }), postcssWriteSvg({ utf8: false }), postcssPresetEnv({}), // postcssViewportUnits({ // filterRule: rule => rule.selector.indexOf('::after') === -1 && rule.selector.indexOf('::before') === -1 && rule.selector.indexOf(':after') === -1 && rule.selector.indexOf(':before') === -1 // }), postcssViewportUnits({}), cssnano({ "cssnano-preset-advanced": { zindex: false, autoprefixer: false }, }) // 添加vw配置 end ], sourceMap: isEnvProduction && shouldUseSourceMap, }, },
這里,配置之后運行項目會發現有個報錯ReferenceError: postcssPresetEnv is not defined
是因為我們沒有引入postcssPresetEnv
安裝並添加以下依賴
npm install postcss-preset-env --save-dev const postcssPresetEnv = require('postcss-preset-env');
配置好了之后,再訪問我們的頁面,可以發現已經自動轉成vw了
4、兼容低版本android,加入viewport-units-buggyfill hack
下載viewport-units-buggyfill.min.js到public文件夾下面,修改index.html添加如下代碼:
<script src='%PUBLIC_URL%/viewport-units-buggyfill.min.js'></script> <script> window.onload = function () { window.viewportUnitsBuggyfill.init({ hacks: window.viewportUnitsBuggyfillHacks }); } </script> 或者使用cdn的方式引入 <script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"></script>
安裝scss
npm install node-sass sass-loader --save
在React中的幾種樣式寫法
行內樣式、聲明樣式、引入樣式、CSS Modules模塊化
1、行內樣式
<div style={{ background: '#eee', width: '200px', height: '200px'}}> <p style= {{color:'red', fontSize:'40px'}}>行內樣式</p> </div>
2、聲明樣式
const style1={
background:'#eee',
width:'200px',
height:'200px'
}
<div style={style1}> <p style= {style2}>行內樣式</p> </div>
3、引入樣式
.person{
width: 60%; margin:16px auto; } import './Person.css'; <div className='person'> <p>person:Hello world</p> </div>
4、css module
CSS Modules 的做法就是通過配置將.css文件進行編譯,編譯后在每個用到css的組件中的css類名都是獨一無二的,從而實現CSS的局部作用域。
在create-react-app2.0之前的版本,配置CSS Modules是需要eject彈出webpack來配置的,幸運的是,create-react-app自從2.0.版本就已經開始支持CSS Modules了
(1)局部樣式
命名規則: xxx.module.css 引入方式 import xxx from 'xxx.module.css' 用法:<div className={xxx.styleName}>
(2)全局樣式
命名規則: xxx.css
引入方式 import ‘xxx.css’
用法:<div className='styleName'>
全局樣式與局部樣式混合使用:
<div className={`styleName ${xxx['styleName']}`} >
其中styleName表示全局樣式 ${xxx['styleName']
表示局部樣式,注意{ }內使用模板字符串 ·
5、css多類名寫法
(1) css module模塊中寫法
<div className={[`${styles.sideInBox}`,`${styles.sideTitleBox}`].join(' ')}></div>
(2) 如果是全局樣式寫法
className={`title ${index === this.state.active ? 'active' : ''}`}
React條件渲染的幾種方式
參考https://www.cnblogs.com/xiaodi-js/p/9119826.html
1、條件表達式
<div> {isLoggedIn ? ( <LogoutButton onClick={this.handleLogoutClick} /> ) : ( <LoginButton onClick={this.handleLoginClick} /> )} </div>
2、&&操作符
<div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div>
3、列表遍歷
jxs的語法,js代碼要放在{}里面,html標簽使用return ()包裹
return ( <div className='appList-container'> <ul className='list'> { this.props.list.map((item, index) => { return ( <li className='list-item' key={index}> <div className='app-index'>{index+1}</div> <img className='app-icon' src={item['im:image'][0].label} alt="" /> <div className='app-info'> <div className='app-name'>{item['im:name'].label}</div> <div className='app-categray'>{item.category.attributes.label}</div> </div> </li> ) }) } </ul> </div> );
事件處理
<button onClick={this.handleClick}>ck me </button>
兩種事件傳參方式
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
獲取input表單值
兩種方法,受控組件和非受控組件。
推薦使用受控組件,即通過this.state獲取,因為其符合react規范;
非受控組件,給標簽指定ref屬性
<input className='search-bar' type="text" ref='keyword' onKeyUp={this.appSearch.bind(this)}> appSearch(e){ let keyword = this.refs.keyword.value }
react中使用防抖
appSearch = debounce(() => { }, 500);
組合組件
參考https://www.jianshu.com/p/0b005dc60bda
在react開發中,在某些場景會遇到如下組件嵌套形式的開發,例如group和cell或者RadioGroup、RadioOption
<RadioGroup name="option"> <RadioOption label="選項一" value="1" /> <RadioOption label="選項二" value="2" /> </RadioGroup>,
state定義及賦值
constructor(props) { super(props); this.state = { appList:[] }; } this.setState({ appList: feed.entry })
父子組件傳參
1、父傳子
<AppList list={this.state.appList}></AppList>
在子組件獲取值
this.props.list
2、子傳父
觸發父組件事件
this.props.appSearch(keyword);
父組件監聽事件
<Search appSearch={this.appSearch.bind(this)}></Search>
引入redux和react-redux、redux-thunk
文檔
https://react-redux.js.org/introduction/quick-start
http://cn.redux.js.org/docs/introduction/ThreePrinciples.html
類似vuex,redux是一個數據狀態管理工具,但是用法和vuex有些區別
react-redux幫助你完成數據訂閱,redux-thunk可以放你實現異步action,redux-logger是redux的日志中間件
redux-thunk 是一個比較流行的 redux 異步 action 中間件。redux-thunk 幫助你統一了異步和同步 action 的調用方式,把異步過程放在 action 級別解決,對 component 沒有影響
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; // 創建store的時候,第二個參數是中間件,redux-thunk提供了一個thunk中間件,用於處理異步的action export default createStore( rootReducer, applyMiddleware(thunk) );
1、對redux的理解
(1)單一數據源:
整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中
(2)State只讀:
唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象
(3)執行修改:
為了描述 action 如何改變 state tree ,你需要編寫 reducers
Reducer 只是一些純函數,它接收先前的 state 和 action,並返回新的 state
隨着應用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分
2、對mapStateToProps和mapDispatchToProps的理解
使用 React Redux 庫的 connect() 方法來生成容器組件前,需要先定義 mapStateToProps 這個函數來指定如何把當前 Redux store state 映射到展示組件的 props 中。
除了讀取 state,容器組件還能分發 action。類似的方式,可以定義mapDispatchToProps() 方法接收 dispatch() 方法並返回期望注入到展示組件的 props 中的回調方法。它可以是一個函數,也可以是一個對象。
// 將state 映射到展示組件的 props 中 const mapStateToProps = state => { return { searchList: state.searchList } } const mapDispatchToProps = dispatch => { return { saveSearchList: searchList => dispatch(saveSearchList(searchList)) } } // export default SearchResult; // 通過connect生成容器組件 export default connect( mapStateToProps, mapDispatchToProps )(SearchResult)
3、安裝redux react-redux redux-thunk
npm install --save redux react-redux redux-thunk npm install --save-dev redux-logger
4、使用react-hot-loader實現局部熱更新
#安裝 npm install --save-dev react-hot-loader #使用 import { AppContainer } from 'react-hot-loader'; import Route from './router/'; const render = Component => { ReactDOM.render( <AppContainer> <Component /> </AppContainer>, document.getElementById("root")); } render(Route);
引入antd-mobile移動端UI框架
antd-mobile文檔
https://mobile.ant.design/index-cn
1、安裝依賴
npm install antd-mobile --save
2、安裝 babel-plugin-import
npm install babel-plugin-import --save
3、在 package.json 配置 antd-mobile 的按需加載(在babel下添加)
"plugins": [ [ "import", { "libraryName": "antd-mobile", "style": "css" } ] ],
4、組件中使用
import { Toast,Button } from 'antd-mobile' <Button type="primary">primary</Button>
上拉刷新及加載更多
這里使用react-pullload這個庫
1、安裝
npm install --save react-pullload
2、使用
import ReactPullLoad, { STATS } from "react-pullload"; import "react-pullload/dist/ReactPullLoad.css"; constructor(props) { super(props); this.state = { appList: [], appListAll: [], recommendList:[], hasMore: true, action: STATS.init, pageSize:10, page:1 }; } handleAction = action => { //new action must do not equel to old action if (action === this.state.action) { return false; } if (action === STATS.refreshing) { this.handRefreshing(); } else if (action === STATS.loading) { this.handLoadMore(); } else { //DO NOT modify below code this.setState({ action: action }); } }; // 刷新 handRefreshing = ()=>{ this.setState({ action: STATS.refreshing }); this.getAppList(); } // 加載更多 handLoadMore = ()=>{ if (STATS.loading === this.state.action) { return false; } //無更多內容則不執行后面邏輯 if (!this.state.hasMore) { return; } // 顯示正在加載 this.setState({ action: STATS.loading }); let page = this.state.page+1; setTimeout(() => { this.getPageData(page); }, 1500); } render() { return ( <div className='container'> <div className='search-bar'> <Search onFoucs={this.onFoucs.bind(this)}></Search> </div> <ReactPullLoad className="block" isBlockContainer={true} downEnough={100} action={this.state.action} handleAction={this.handleAction} hasMore={this.state.hasMore} distanceBottom={100}> <Recommend list={this.state.recommendList}></Recommend> <AppList list={this.state.appList}></AppList> </ReactPullLoad> </div> ); }
因為是使用的mock數據,獲取的是全部數據,所以這里采用前端分頁的方式加載更多
// 分頁加載 getPageData(page){ let resultList = [], list = []; let appListAll = this.state.appListAll; let pageSize = this.state.pageSize; let totalPage = Math.ceil(appListAll.length / pageSize);//總頁數 let startIndex = pageSize * (page - 1); let endIndex = pageSize * page; for (let i = startIndex; i < endIndex; i++) { resultList.push(appListAll[i]); } if (page >= totalPage){ this.setState({ hasMore: false}); } if (page===1){ list = resultList; }else{ list = this.state.appList.concat(resultList); } this.setState({ appList: list, page: page, pageSize: pageSize, action: STATS.reset }) }
圖片懶加載
http://npm.taobao.org/package/react-lazy-load
1、安裝
npm install --save react-lazy-load
2、使用
import LazyLoad from 'react-lazy-load';
<LazyLoad offsetVertical={100}> <img className='app-icon' src={item['im:image'][0].label} alt="" /> </LazyLoad>
問題總結
1、在react中進入頁面自動獲取input輸入焦點 ,彈出鍵盤
input中設置ref屬性(非受控組件),通過 this.refs.keyword調用
<input className='search-input' type="text" ref='keyword' onChange={this.appSearch.bind(this)} onFocus={this.onFoucs.bind(this)} placeholder="搜索應用" /> 也可以寫成ref={(input) => { this.textInput = input; }}方式 <input type="text" ref={(input) => { this.textInput = input; }} /> 使用this.textInput.focus();方式調用 鈎子函數中中調用 componentDidMount(){ this.refs.keyword.focus(); }
2、父組件調用子組件方法(搜索組件,有兩個地方使用到,首頁和搜索頁,其中首頁不需要自動獲取焦點,進入搜索頁時需要自動獲取焦點)
通過在搜索結果頁里面獲取搜索子組件的實例並調用foucs方法進行聚焦,從而不影響其他使用搜索組件的父組件狀態
(1)子組件中定義foucs方法 focus(){ this.refs.keyword.focus(); } (2)設置input的ref屬性 <input className='search-input' type="text" ref='keyword' onChange={this.appSearch.bind(this)} onFocus={this.onFoucs.bind(this)} placeholder="搜索應用" /> (3)父組件中調用foucs方法 componentDidMount(){ this.manualFocusInst.focus(); } <Search appSearch={this.appSearch.bind(this)} ref={(ref)=>this.manualFocusInst = ref} onCancel={this.onCancel.bind(this)} onFoucs={this.onFoucs.bind(this)} showCancelBtn={true}></Search>
3、react build的時候報錯
throw new BrowserslistError('Unknown browser query `' + selection + '`')
解決辦法是找到packpage.json里的browserslist,然后修改
"browserslist": [ "last 2 versions", "android 4", "opera 12" ],
build開啟靜態服務訪問
npm install -g serve serve -s build
4、組件上面不能直接添加className,如
解決方式使用一個父div進行包裹
<div className='search-bar'> <Search onFoucs={this.onFoucs.bind(this)}></Search> </div>
5、ios 系統下img不顯示問題,解決方案如下:
/*兼容ios不顯示圖片問題*/ img { content: normal !important }
6、1px問題,解決方案
/*偽元素1px*/ .row-cell:before { content: " "; position: absolute; left: 0; top: 0; right: 0; height: 1px; border-top: 1px solid #e5e5e5; color: #e5e5e5; transform-origin: 0 0; transform: scaleY(0.5); z-index: 2; }
相關文檔
https://react.docschina.org/
https://www.redux.org.cn/
https://react-redux.js.org/
http://react-guide.github.io/react-router-cn
https://mobile.ant.design
最后
代碼我已經提交到github上去了,如果覺得還可以,歡迎star或者fork
參考閱讀
https://www.jianshu.com/p/8954e9fb0c7e
https://blog.csdn.net/z9061/article/details/84619309
https://www.jianshu.com/p/f97aa775899f
https://www.cnblogs.com/jack-liu6/p/9927336.html
文章出處:https://www.cnblogs.com/fozero