react技术栈实践(2)
本文来自网易云社区
作者:汪洋
这时候还没完,又有两个问题引出来了。
按照上面的配置,第三方库 antd 竟然也被编译了,导致样式失败。
react中,一旦包裹了子组件,子组件没办法直接使用 styleName。
第2个问题,还好解决,查了下 react-css-modules 资料,子组件中通过props获取
const template = ( <div className={this.props.styles['loadingBox']}>
<Loading />
</div>);
第1个问题纠结了好久,后来找了个折中的方案,好心酸。 在entry.jsx中引入的antd组件样式,改成
import 'antd/dist/antd.css';
对,直接引入 css文件,跳过less编译。
然后在webpack中新增配置
{
test: /\.(css|less)$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [ 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'less-loader'
]
}),
exclude: /node_modules/
},
{
test: /\.(css)$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [ 'css-loader'
]
}),
include: /node_modules/
},
到这一步,大家应该明白我的方案了,就是 node_modules 文件夹中的 css文件不启动 cssmoduls,其它文件夹中 启动 cssmoduls。
接下来就是第4个大问题待解决,路由按需加载。
作为新手,当然首先是搜索一下 react-router 4.x 如何实现按需加载的,果然好多答案。至于如何选择,当然是哪个方便哪个来的原则。 react-loadable 这个插件,当然这个货得依赖 babel-plugin-syntax-dynamic-import 包。
webpack配置,加入 babel的 syntax-dynamic-import插件
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
query: {
presets: ['es2015', 'react', 'stage-0'],
plugins: ['syntax-dynamic-import']
}
}
]
},
...
react中使用 react-loadable,特别方便
import Loadable from 'react-loadable';
...const MyLoadingComponent = ({isLoading, error, pastDelay}) => { // Handle the loading state
if (pastDelay) { return <div>Loading...</div>;
} // Handle the error state
else if (error) { return <div>Sorry, there was a problem loading the page.</div>;
} else { return null;
}
}const AsyncTestManager = Loadable({
loader: () => import('./pages/TestManager/Index'),
loading: MyLoadingComponent
}); ReactDOM.render( <Provider store={Store}>
<BrowserRouter basename="/" forceRefresh={!supportsHistory} keyLength={12}>
<div>
<Route exact path="/testManager" component={AsyncTestManager}/>
</div>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
这个插件具体使用大家查看相关文档,很方便强大。记得上线打包的时候,webpack要启动hash
output: {
filename: '[name][chunkhash].js',
path: BUILD_PATH,
chunkFilename: '[name][chunkhash].js',
publicPath: './'
},
至此,脚手架搭建走过的坑结束了。
顺便提下
output: {
...
publicPath: '../'
},
这里一定要配置为 ../ ,不要配置为 ./,因为不小心配错,导致路由按需加载的时候,js路径错误了。
实战阶段
这里要介绍下 redux的一个中间件,redux-thunk。何为中间件,以及 redux-thunk的作用,大家可以参考下阮一峰的一篇教程《Redux 入门教程(二):中间件与异步操作》 。 正常情况下,actions返回的只是一个对象,但是我们想发送数据前最好能处理下,所以呢,就需要重写下Store.dispath方法了。中间件就是这样的作用,改写 dispatch,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。比如异步操作:发起ajax请求。视图发起一个action,触发了一个请求,但是action不能返回函数,这时候redux-thunk就起作用了。
Store初始化
这个过程,就是把 reducer跟Store绑定在一起,同时引入需要的中间件
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducers from '../reducers';const store = applyMiddleware(
thunkMiddleware
)(createStore)(reducers); export default store;
applyMiddleware 方法它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。 createStore 方法创建一个 Store。 至于这个参数写法,其实就是es6的柯里化语法。用es3,es5实现其实原理很简单,就是利用了闭包保存了上一次的数据,实现过单列模式的同学应该很清楚。
function add(number1) { return function(number2) { return number1 + number2;
};
}var addTwo = add(1)(2);
Reducer实例
至于Reducer,其实很好实现,它其实就是单纯的函数。
例如:
import * as CONSTANTS from '../../constants/TestControl';const initialState = {};const testControl = (state = initialState, action) => { switch (action.type) { case CONSTANTS.GET_DETAILS_PENDING: return {
...state,
isFetching: true,
data: action.payload,
success: false
}; case CONSTANTS.GET_DETAILS_SUCCEEDED: return {
...state,
isFetching: false,
data: action.data.relatedObject,
success: true
}; case CONSTANTS.GET_DETAILS_FAILED: return {
...state,
isFetching: false,
success: false,
errorCode: action.data.errorCode
}; default: return state;
}
}; export default testControl;
大家应该注意到,这个其实是对应action的一个ajax请求,其中,action.type中 ,
_PENDING 结尾的表示 ajax正在发起请求;
_SUCCEEDED 结尾的表示 ajax 请求成功;
_FAILED 结尾的表示 ajax 请求失败;
这个我是作为ajax actions的标准命名,大家也可以用其它方式,原则就是:好理解,统一。 当然其它非ajax的actions(包括ajax的action),我的规则就是,命名要表意,常量要大写。
由于我的项目中reduce有n个,所以 reducers/index.js 是这样的
import { combineReducers } from 'redux';
import testManagerList from './TestManager/list';
import common from './Common';
import system from './System';
import evaluate from './Evaluate';
import ComponentsAddLayer from './Components/addLayer';
import testNew from './TestNew';
import testControl from './TestControl'; export default combineReducers({
testManagerList,
system,
evaluate,
ComponentsAddLayer,
testNew,
common,
testControl
});
引入 redux 的combineReducers 方法,这样就把多个 reducer集合到一起了,调用state的时候,只要如此:
const mapStateToProps = state => ({
type: state.testManagerList.type
});
大家看明白了吧,testManagerList 是我的一个 reducer。
actions
Actions 我是作为存放数据的,比如ajax数据请求,视图默认数据这些。
const testManager = {
testManager_get_list(options) { return (dispatch) => { const fetchData = axios.get('/abtest/getList', options);
dispatch({
type: TABLE_GET_LIST_PENDING,
payload: fetchData
});
fetchData.then((response) => { if (response.data.success) {
dispatch({
type: TABLE_GET_LIST_SUCCEEDED,
...response
});
} else {
dispatch({
type: TABLE_GET_LIST_FAILED,
...response
});
}
}).catch((error) => {
dispatch({
type: TABLE_GET_LIST_FAILED,
...error
});
});
};
},
testManager_change_tabs(activeTabs) { return {
type: TABS_CHANGE,
active: activeTabs
};
},
testManager_search(value) { return {
type: SEARCH,
keyWord: value
};
},
testManager_parameters(options) { return {
type: TEST_MANAGER,
parameters: Object.assign({}, {
page: 1,
pageSize: 10,
sort: '',
type: '',
keyWord: ''
}, options || {})
};
},
testManager_pagination_change(noop) { return {
type: PAGINATION_CHANGE,
page: noop
};
}
};
这个模块触发的actions:获取表格列表数据,搜索,分页操作,获取默认配置,很好理解,这里就不说了。 具体如何使用,请看下面的 view 实践
View实践
开始的时候,提出几个问题:
视图如何跟Store绑定;
ACTIONS如何在视图中使用;
引入的第三方组件样式有什么好的方式修改;
视图中的props如何获取路由信息;
先解决第3个问题,一开始我是想重写覆盖第三方的css文件的,后来一看代码量,果断放弃了。还好被我发现了 styled-components 这个插件,果然好用。
import styled from 'styled-components';
import Tabs from 'antd/lib/tabs';const TabsStyle = styled(Tabs)`
float: left;
.ant-tabs-nav-wrap {
margin-bottom: 0;
}
.ant-tabs-tab { text-align: center;
transition: background 0.3s;
color: #666666;
padding: 6px 12px;
font-size: 14px;
font-weight: 400;
cursor: pointer;
user-select: none;
background-image: none;
margin-left: -10px;
}
`;
这里面跟写less一样就好了。我是这么觉得。具体大家可以查看下对应的文档。开发过react-native的同学,都很清楚这个插件的给力。
再结晶第4个问题。react-router 官方提供了 withRouter的api,这个api就是专门为了解决这个问题。
import CSSModules from 'react-css-modules';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
......
componentDidMount() { // props中就可拿到路由信息了
const { ACTIONS, match } = this.props;
ACTIONS.TestControl_get_testing_detail({ id: match.params.id });
}const turnCss = CSSModules(TestManager, styles, { allowMultiple: true });
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));
非常方便。
再来说第一个问题,视图如何跟Store绑定 Store提供了三个方法
store.getState()
store.dispatch()
store.subscribe()
其中,Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。所以绑定视图,调用这个方法就好了。 不过redux作者专门针对react,封装了一个库:React-Redux,这里我就直接引用了,这样我就不用处理state了。
import { connect } from 'react-redux';const mapStateToProps = state => ({
isFetching: state.testControl.isFetching,
success: state.testControl.success,
detail: state.testControl.data
});const mapDispatchToProps = dispath => ({
ACTIONS: bindActionCreators(actions, dispath)
});const turnCss = CSSModules(TestControl, styles, { allowMultiple: true });
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));
这样 TestControl 视图就跟 Store绑定到一起了。 具体的API介绍,大家可以查看下文档,还是很好理解的。
解决了第一个问题,再来看第2个问题:ACTIONS如何在视图中使用
ACTIONS的作用,其实就是消息订阅/发布 模式中,发布那个步骤了。这样理解,大家应该明白了吧, 比如: 视图中点击了一个按钮后,回调函数中就直接调用对应的ACTIONS方法即可。
还要介绍下redux的bindActionCreators方法:
主要用处:
一般情况下,我们可以通过Provider将store通过React的connext属性向下传递,bindActionCreators的唯一用处就是需要传递action creater到子组件,并且该子组件并没有接收到父组件上传递的store和dispatch。
import { bindActionCreators } from 'redux'; import actions from '../../actions';class TestControl extends Component {
componentDidMount() { const { ACTIONS, match } = this.props;
ACTIONS.TestControl_get_testing_detail({ id: match.params.id });
} // 开始
start() { const { ACTIONS, match } = this.props;
ACTIONS.TestControl_start({ id: match.params.id });
}
render() {
...
}
}const mapStateToProps = state => ({
isFetching: state.testControl.isFetching,
success: state.testControl.success,
detail: state.testControl.data
});const mapDispatchToProps = dispath => ({
ACTIONS: bindActionCreators(actions, dispath)
});const turnCss = CSSModules(TestControl, styles, { allowMultiple: true });
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));
至此,redux实践结束。
nginx配置
因为是单页面模式,且使用了 BrowserRouter,故nginx配置如下:
location / {
root E:/program/ark2/abtest-statics/build/;
index index.html index.htm;
expires -1;
try_files $uri $uri/ /entry.html;
}
其它
开发一个项目,最好需要一个合理的约定,比如代码风格、模块定义、方法定义、参数定义等等,这些约定中,还要考虑如何便于写和维护单元测试这个因素。这些其实还是挺有挑战的,只能不断去完善。
上面方案其实还有很多缺陷待解决,需要慢慢改进了。
相关文章:react技术栈实践(1)
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 网易云社区有奖问答活动第二期——技术领导力、深入分布式、PHP圣经、Linux运维、Unity……三月热点图书等你拿!
【推荐】 网易杭研易盾实习心得
react技术栈实践(2)的更多相关文章
- react技术栈实践(1)
本文来自网易云社区 作者:汪洋 背景 最近开发一个全新AB测试平台,思考了下正好可以使用react技术开发. 实践前技术准备 首先遇到一个概念,redux.这货还真不好理解,大体的理解:Store包含 ...
- 重谈react优势——react技术栈回顾
react刚刚推出的时候,讲react优势搜索结果是几十页. 现在,react已经慢慢退火,该用用react技术栈的已经使用上,填过多少坑,加过多少班,血泪控诉也不下千文. 今天,再谈一遍react优 ...
- node.js+react全栈实践-Form中按照指定路径上传文件并
书接上回,讲到“使用同一个新增弹框”中有未解决的问题,比如复杂的字段,文件,图片上传,这一篇就解决文件上传的问题.这里的场景是在新增弹出框中要上传一个图片,并且这个上传组件放在一个Form中,和其他文 ...
- node.js+react全栈实践
利用业余时间写了个简单的项目,使用react+node.js做的一个全栈实践项目,前端参考了[React-Admin-Starter](https://github.com/veryStarters/ ...
- 深入React技术栈之初入React世界
1.1 react简介 react并不是完整的MVM/MVVM框架,专注于提供清晰.简洁的View层解决方案. 传统开发模式,要更新页面需要手动操作DOM元素.如图1.1所示,React在DOM上封装 ...
- React技术栈梳理
一.react是什么? react是一个js框架,可以用它来编写html页面,使用react后我们可以完全抛弃html(只需要一个主index文件),而用纯js来编写页面: 二.为什么要使用react ...
- 深入react技术栈解读
1. react实现virtual DOM ,如果要改变页面的内容,还是需要执行DOM操作,比原生操作DOM多了virtualDOM的操作(计算,对比等), 应该是更耗性能??? 2. react特点 ...
- 深入React技术栈之setState详解
抛出问题 class Example extends Component { contructor () { super() this.state = { value: 0, index: 0 } } ...
- React技术栈——Redux
Redux 1.Redux是什么? Redux对于JavaScript应用而言是一个可预测状态的容器.换言之,它是一个应用数据流框架,而不是传统的像underscore.js或者AngularJs ...
随机推荐
- spring data jpa自定义baseRepository
在一些特殊时候,我们会设计到对Spring Data JPA中的方法进行重新实现,这将会面临一个问题,如果我们新创建一个实现类.如果这个实现类实现了JpaRepository接口,这样我们不得不实现该 ...
- vue-quill-editor上传内容由于图片是base64的导致字符太长的问题解决
vue-quill-editor是个较为轻量级富文本框,相较于ueditor,开发更编辑,更加直观,如果大家伙在需求允许的情况下,还是会比较建议使用vue-quill-editor vue-quill ...
- [选择排序] 时间复杂度O(n^2)
思路:从未排序的序列中,找到最小的元素,放到序列的起始位置, 再从剩下没排序的里面,找到最小的,放到已经排序的末尾. 原地操作几乎是选择排序的唯一优点,当空间复杂度要求较高时,可以考虑选择排序:实际适 ...
- django之基于cookie和装饰器实现用户认证
示例1 # Create your views here. user = "a" pwd = "a" def login(request): if reques ...
- HTTP1.0工作原理
1.HTTP工作原理 <HTTP响应报文与工作原理详解>讲的比较详细了. 2.示例 (1)server端程序如下: package org.yeyouluo.demo.jsp; impor ...
- JavaScript 的 parseInt 取整
http://www.neoease.com/javascript-get-integer-via-parseint/ JavaScript 是弱类型语言, 为了保证数值的有效性, 在处理数值的时候, ...
- 《spss统计分析与行业应用案例详解》:实例十二 卡方检验
卡方检验的功能与意义 SPSS的卡方检验是非参数检验方法的一种,其基本功能足通过样本的 频数分布来推断总体是否服从某种理论分布或某种假设分布,这种检验过程是通过分析实际的频数与理论的频数之间的差别或是 ...
- halt, reboot, poweroff - 中止系统运行
SYNOPSIS /sbin/halt [-n] [-w] [-d] [-f] [-i] [-p] /sbin/reboot [-n] [-w] [-d] [-f] [-i] /sbin/powero ...
- Problem D: 小平查密码
Problem D: 小平查密码 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 194 Solved: 40[Submit][Status][Web ...
- Java的日期类和日期格式化类
日期类: Date date = new Date(); // 获取当前的系统时间 2 System.out.println("年份:"+ date.getYear()); Cal ...