结构-行为-样式 - React + Redux + Webpack + Antd 小Demo
最近在学习React,从小白到入门,谈不上精通。每一个新的框架都是一样,主要是自己有自己的学习方法,再多的框架也是手到擒来。React主要的思想就是组件开发,官网上的Demo也说的很详细,就是让你在做一个页面之前,先想想,这个东西能拆分成什么模块,子模块,怎样嵌套。设计的思路理清楚之后才是动手写代码,同时,作为一个框架,它采取的是一种包容的态度。一个Demo下来,除了官方的那几个Js之外,其他大部分都是第三方的插件。Redux就是Flux的改编版本。不费话了,来看看我的这个Demo吧:
首先,咱们来看下整体目录结构:
一、先看看最终的样子:
其实就是套用了Antd的一个布局架子,加上表格和自定义的搜索栏,基本上实现了增删改查的功能。这个页面包括搜索模块与表格模块,这里咱们只说表格模块,搜索模块类似。
二、第一层,也就是整个页面的外层,调用 的是Antd的布局代码(布局 Layout - Ant Design):
import React from 'react'; import NavLink from './NavLink'; import { Layout, Menu, Breadcrumb, Icon } from 'antd'; const { SubMenu } = Menu; const { Header, Content, Sider } = Layout; export default React.createClass({ render(){ return( <Layout> <Header className="header"> <div className="logo" > <img src={require("../images/logo.png")} alt="" width="112" height="35" /> </div> <Menu theme="dark" mode="horizontal" defaultSelectedKeys={['3']} style={{ lineHeight: '64px' }} className="headerMenu" > <Menu.Item key="1"><NavLink to="/" >首页</NavLink></Menu.Item> <Menu.Item key="2"><NavLink to="/about">报表</NavLink></Menu.Item> <Menu.Item key="3"><NavLink to="/repos/pageb" onlyActiveOnIndex>配置</NavLink></Menu.Item> </Menu> </Header> <Layout> <Sider width={200} style={{ background: '#BD2626' }}> <Menu mode="inline" theme="dark" defaultSelectedKeys={['1']} defaultOpenKeys={['sub1']} style={{ height: '100%' }} className="diy-dark" > <SubMenu key="sub1" title={<span><Icon type="user" />权限管理</span>}> <Menu.Item key="1"><NavLink to="/repos/pagea">组织架构</NavLink></Menu.Item> <Menu.Item key="2"><NavLink to="/repos/pageb">用户管理</NavLink></Menu.Item> <Menu.Item key="3">角色管理</Menu.Item> </SubMenu> <SubMenu key="sub2" title={<span><Icon type="laptop" />参数配置</span>}> <Menu.Item key="5">功能配置</Menu.Item> <Menu.Item key="6">业态配置</Menu.Item> <Menu.Item key="6">特征配置</Menu.Item> <Menu.Item key="6">码表配置</Menu.Item> </SubMenu> <SubMenu key="sub3" title={<span><Icon type="notification" />日志管理</span>}> <Menu.Item key="9">接口日志</Menu.Item> <Menu.Item key="10">操作日志</Menu.Item> </SubMenu> </Menu> </Sider> <Layout style={{ padding: '0 24px 24px' }}> <Breadcrumb style={{ margin: '12px 0' }}> <Breadcrumb.Item>配置</Breadcrumb.Item> <Breadcrumb.Item>权限管理</Breadcrumb.Item> <Breadcrumb.Item>组织架构</Breadcrumb.Item> </Breadcrumb> <Content style={{ background: '#fff', padding: 15, margin: 0, minHeight: 515 }}> {this.props.children} </Content> </Layout> </Layout> </Layout> )} });
注:整个Js看作是一个组件,子组件中Content下面。
三、第2-1层,表格组件:
3.1、首先,定义两个自定义事件,删除与修改:
//删除一条记录 handleDelete(record){ this.props.dispatch(action.doDelete({record,data})); } //修改一条记录 handleModify (record) { record.title = "修改记录"; record.modalType = "modify"; let isVisible = true; this.props.dispatch(action.setVisible(isVisible)); let current = record; this.props.dispatch(action.showModal({current,data})); }
3.2、然后在构造函数中绑定我们的自定义事件,得到当前对象:
//构造函数 constructor(props) { super(props); this.handleDelete = this.handleDelete.bind(this); this.handleModify = this.handleModify.bind(this); }
3.3、调用Antd的表格组件,进行表格的组装,事件在columns时就可以先设定好,具体如下:
const columns = [{ title: '姓名', dataIndex: 'name', key: 'name', render: text => <a href="javascript:;">{text}</a>, }, { title: '邮箱', dataIndex: 'email', key: 'email', }, { title: '手机', dataIndex: 'phone', key: 'phone', },{ title: '地址', dataIndex: 'address', key: 'address', }, { title: '操作', key: 'action', render: (text, record) => ( <span> <a href="javascript:;" onClick={()=>this.handleModify(record)} >修改</a> <span className="ant-divider" /> <a href="javascript:;" onClick={()=>this.handleDelete(record)} >删除</a> </span> ), }];
3.4、最后Render出来的虚拟Dom就一行:
return ( <Table columns={columns} dataSource={data} /> );
四、事件处理与状态传递(Redux相关):
4.1、Redux工作原理(盗图一张):
那些很专业的话就不说了,这里就说实际Demo中的对应关系。实际开发中,应该要定义Action、Reducer、Store三个文件,它们工作流程就是上面图中所示。
4.2、表格相关 Action:
//修改记录 export const doModify = ({current,data})=>{ for(let i=0;i<data.length;i++){ if(current.key == data[i].key){ data[i] = current; data[i].address = current.residence.join(" - "); break; } } const list = data; return { type:'DO_MODIFY', list } } //删除记录 export const doDelete = ({record,data})=>{ for(let i=0;i<data.length;i++){ if(data[i].key == record.key){ data.splice(i,1); break; } } let list = data; return { type:'DO_DELETE', list } }
注:应该把当前动作所包含的逻辑写在Action中,不要写在Reducer中。触发Action的地方应该是在组件的文件中,通过dispatch方法来触发 :this.props.dispatch(action.showModal({current,data})); 类似这个。
4.3、增删改查的 Reducer:
const doHeader = (state='',action)=>{ switch(action.type){ case 'DO_SUBMIT': return {"list":action.list}; case 'DO_ADD': return {"list":action.list}; case 'DO_MODIFY': return {"list":action.list}; case 'DO_DELETE': return {"list":action.list}; default: return state; } }
注:建议把一个组件的Reducer放在一起,最终Store调用 的就是一个总的Reducer。
4.4、只有一个Store:
import {createStore} from 'redux'; import todoApp from '../reducers'; let store = createStore( todoApp ); export default store;
注:这里的todoApp就是Reducers的总入口。
所以,最终的思路就是,在组件的文件中,有一个事件,通过 dispatch 分发到Action,处理完之后通过Reducer反映到全局的State中,直接的就是另一个组件监听到它需要 的State改变了,反映到页面。整个过程都是单向的,一个组件中的Props值是不可以改变的。
五、webpack配置:
5.1、开发模式和发布模式:
5.1.1、开发模式会定义devserver和热更新,具体如下:
devServer: {//webpack-dev-server 配置 contentBase: "./server",//本地服务器所加载的页面所在的目录 port: 8888, colors: true,//终端中输出结果为彩色 historyApiFallback: true,//不跳转 inline: true, hot:true//热更新 }, postcss:[ autoprefixer({browsers:['last 10 versions']})//postcss 插件 ], plugins:[ new webpack.BannerPlugin('Copyright Chvin'),//添加 js头 new webpack.HotModuleReplacementPlugin()//热更新 ]
5.1.2、发布模式则不用那么麻烦,定义一下文件导出格式与入口文件:
plugins: [ new HtmlWebpackPlugin({ template: __dirname + "/server/index.tmpl.html" }), new webpack.optimize.UglifyJsPlugin(), new ExtractTextPlugin("[name]-[hash].css"), new webpack.optimize.DedupePlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV':'"production"' }) ],
注:这里省略了其他配置,只是写出了要注意的地方
5.2、打包与调试命令配置(在Package.json中),start是本地开发命令,build就是发布文件生成:
"scripts": { "start": "webpack-dev-server --progress && echo sddss", "build": "webpack --config ./webpack.production.config.js --progress" },
六、路由配置:
6.1、引用路由:
import { Router, Route, hashHistory, IndexRoute } from 'react-router';
6.2、首页、嵌套。Router中包含Route,首页的路由可以通过IndexRoute来定义,同时,要定义路由中对应的组件。具体如下:
render(
<Provider store={store} >
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="/repos" >
<Route path="/repos/pagea" component={About}/>
<Route path="/repos/pageb" component={Counter}/>
</Route>
<Route path="/about" component={About}/>
</Route>
</Router>
</Provider>
,document.getElementById('root'));
完整Demo下载:https://github.com/chickentang/GIT_TANG
有写的不对的地方欢迎留言。。。