本文来自网易云社区css
做者:汪洋html
这时候还没完,又有两个问题引出来了。 node
按照上面的配置,第三方库 antd 居然也被编译了,致使样式失败。react
react中,一旦包裹了子组件,子组件没办法直接使用 styleName。
webpack
第2个问题,还好解决,查了下 react-css-modules 资料,子组件中经过props获取ios
const template = ( <div className={this.props.styles['loadingBox']}> <Loading /> </div>);
第1个问题纠结了很久,后来找了个折中的方案,好心酸。 在entry.jsx中引入的antd组件样式,改为 nginx
import 'antd/dist/antd.css';
对,直接引入 css文件,跳过less编译。
而后在webpack中新增配置es6
{ 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。web
接下来就是第4个大问题待解决,路由按需加载。
做为新手,固然首先是搜索一下 react-router 4.x 如何实现按需加载的,果真好多答案。至于如何选择,固然是哪一个方便哪一个来的原则。 react-loadable 这个插件,固然这个货得依赖 babel-plugin-syntax-dynamic-import 包。
webpack配置,加入 babel的 syntax-dynamic-import插件ajax
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就起做用了。
这个过程,就是把 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,其实很好实现,它其实就是单纯的函数。
例如:
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 我是做为存放数据的,好比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 实践
开始的时候,提出几个问题:
视图如何跟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实践结束。
由于是单页面模式,且使用了 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+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 教你如何选择BI数据可视化工具
【推荐】 扫脸动画
【推荐】 canvas 动画库 CreateJs 之 EaselJS(下篇)