使用react+redux+react-redux+react-router+axios+scss技术栈从0到1开发一个applist应用

摘要:

先看效果图

github地址

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

github仓库

在线访问

参考阅读

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

github地址

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

github仓库

在线访问

参考阅读

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

猜你喜欢

转载自www.cnblogs.com/onesea/p/13169911.html