这几天用react+redux+webpack写了一个简单的邮箱,这是我第一次用redux写应用,以为颇有必要记录一下遇到的各类坑~~??css
DEMO在这里:
https://yisha0307.github.io/M...
这边是源码:
https://github.com/yisha0307/...
(各位用github的旁友路过请随意帮我点个赞哟!谢谢~)html
外观如图:node
webpack基础的配置环境能够先看我这篇文章:
https://segmentfault.com/a/11...react
而后再来说讲此次特别的地方。webpack
考虑到最后bundle.js的大小问题,第三方库我都用的cdn, 此次用到的有:
react / redux / react-dom / react-redux / font-awesome,
除了最后一个,其余的四个都要在webpack.config.js里用externals注明一下:git
//webpack.config.js externals: { "react":"React", "redux":"Redux", "react-dom" :"ReactDOM", "react-redux":"ReactRedux" }
而后在写的js/jsx文件里开头引用一下就行:github
//相似这样的格式: import React,{Component} from 'react'
font-awesome由于是css,原本就是全局的,因此就不须要externals,直接用就行了~web
这个plugin也是为了缩小最后的bundle.js的~
不过由于装了这个plugin以后热加载的速度会变慢,因此建议开发的时候先不要用~
另外还有一些办法可让bundle.js变小,好比关掉devtool之类的,具体能够看我以前写的一篇笔记:
https://segmentfault.com/n/13...数据库
此次整个的应用我是这么安排的:json
node_modules不说了,反正基本不用管;
public里面放的是最后的bundle.js和index.html,和我本身作头像的一张照片嘿嘿~
src里就是主要写的东西啦~由于是用react-redux的provider和connect写的,因此分红了containers和components,components放UI组件,containers放容器组件;
css我用的是sass,此次试了下css-module,也挺容易的,只要在webpack.config.js里面的css-loader后面加上?modules
就能够用css-module了,
具体用法:https://segmentfault.com/n/13...
由于没有服务器端,此次的邮件就用inbox.json这个文件模拟;
reducers.js记录此次使用的全部reducer,最后用redux里的combineReducers
合并成一个,用createStore
引入到<Provider store={store}>
里。
先看一眼我此次的reducers:
//import MAILS from './src/inbox.json'; import {combineReducers} from 'redux' import MAILS from './src/inbox.json' //一、mails //数据库里全部的Mails(包括显示的和没显示的) //先对MAILS进行处理,每一个加上一个id let id = 0 for(const mail of MAILS){ mail.id = id++; } console.log(MAILS); const mails = (state = MAILS, action) => { switch(action.type){ case 'COMPOSE': return [...state, {from: action.from, address: action.address, time:action.time, message: action.message, subject:action.subject, id: id++, tag: action.tag, read:'true'}] case 'DELETE_MAIL': //根据id把这封邮件找出来,tag改为'deleted' return state.map(mail => { if(mail.id !== action.id){return mail;}else{ return(Object.assign({}, mail, {"tag": "deleted"})); } }) case 'OPEN_MAIL': return state.map(mail => { if(mail.id !== action.id){return mail;}else{ return(Object.assign({},mail,{"read":"true"})); } }) default: return state } } //二、currentSection //显示在mailist里的mails const currentSection = (state = 'inbox', action) => { switch(action.type){ case 'SELECT_TAG': return action.tag; default: return state } } //三、selected //显示在maildetail里的那封邮件 const selectedEmailID = (state = null, action) => { switch(action.type){ case 'OPEN_MAIL': return action.id; case 'DELETE_MAIL': const mails = action.mails const selected= mails.find(mail => mail.tag === action.tag && mail.id > action.id); if(!selected){return null} return selected.id case 'SELECT_TAG': return null default: return state } } //四、composeORnot //若是值为true,maillist和maildetail不出现,只出现composepart //若是值为false, 反过来 const composeORnot = (state = false,action) => { switch(action.type){ case 'TURN_COMPOSE': return !state; case 'SELECT_TAG': return false default: return state } } //五、新加一个unread const showUnread = (state = false,action) => { switch(action.type){ case 'TURN_UNREAD': return action.bool; default: return state } } const inboxApp = combineReducers({mails,currentSection,selectedEmailID,composeORnot,showUnread}); export default inboxApp
此次用的reducers:
1) mails:
COMPOSE: 每写一封邮件在原来的mails后面插入一封;
DELETE: 目标邮件的tag改为'deleted';
OPEN:目标邮件的'read'改为'true'.
2) currentSection:
SELECT_TAG:在右边栏选到哪一个tag就render那个tag下的邮件队列;
3)selectedEmailID:
OPEN_MAIL: open的为目标邮件;
DELETE_MAIL: delete的邮件的下一封且同tag的邮件选为目标邮件;
SELECT_TAG:选其余的tag,select的mail就取消,等待用户选择;
4)composeORnot:
TURN_COMPOSE: 就是在页面上点'compose'这个按钮,mailList和mailDetail不出现,出现的是compose邮件的地方;
SELECT_TAG:在右边栏选tag的时候,自动回到mailList和mailDetail;
5) showUnread
其实就是在页面上的'all'和'unread'切换;
reducers其实就是用来记录state的变化,因此写react会用到几个state, 这边就须要几个reducers~ 不过有一点不同的是,若是是写react应用,思惟逻辑是从action => state的变化,可是react+redux就是从state的变化 => action。
举个例子来讲,若是我在react里写delete mail这个action,是这样的:
//react w/o redux deleteEmail(id){ const emails = this.state.emails; const index = emails.findIndex(x=>x.id === id); emails[index].tag='deleted'; selectedEmail = emails.find( x=> x.tag===emails[id].tag && x.id > id); this.setState({ selectedEmail: selectedEmail, emails }) }
这个deleteEmail的动做其实影响到了
emails(选中的这封mail的tag变成'deleted')
selectedEmail(自动选同一个tag队列里的下一封邮件)
两个state。可是写的时候,逻辑实际上是从actions的角度出发的。
可是若是用redux写,就是从state的角度出发考虑,能够看我上面的reducers里的mails和selectedEmailId两个state里都有DELETE_MAIL
这个action。
另外设计states的时候,要尽可能减小各个states之间的耦合,由于它们之间在reducers.js里是无法互相引用的;可是若是实在无法彻底剥离开也是有办法解决的。好比我上面写的selectedEmailId
这个reducer,在DELETE_MAIL这个动做发出以后,要选择下一封邮件做为target,可是不能直接用action.id+1, 由于无法肯定在inbox.json文件里,队列里下一个mail的tag是和你删掉的同样的,因此这时候我选择把mails当作参数传进去:
case 'DELETE_MAIL': const mails = action.mails //注意这个 const selected= mails.find(mail => mail.tag === action.tag && mail.id > action.id); if(!selected){return null} return selected.id
用的时候就直接把mails放在mapStateToProps, 而后再传给dispatch就行~就能够解决两个state互相引用的问题啦~
此次是用react-redux这个库来链接的,固然也能够选择不用react-redux,直接在root组件上把全部的action都dispatch一下,而后一级一级传下去。
react-redux这个库也很好理解,主要使用了Provider和connect两个方法.
使用provider就是让全部的组件有取到redux里保存的state的可能性。只要在root组件外面包一层就能够。须要的属性就是store。
//index.jsx const store = createStore(inboxApp) class App extends Component{ render(){ return( <Provider store={store}> <Mailbox /> </Provider>) } }
connect就是一个比较重要的方法啦,它的意思就是把容器组件和UI组件联系在一块儿。这样只要写好UI组件,外面用connect()包一层就行啦~
这个应用的UI组件和容器组件是这样的:
前面加大v的就是容器组件,基本都是一一对应的关系,固然有些不须要逻辑层的能够只用UI组件就行,好比MailItem这个~
拿Sidebar举个例子(截取部分):
//sidebar.jsx const Sidebar =({currentSection, unreadcount,trashcount,sentcount,handleCategory,turncompose}) => { return ( <div className={styles.sidebar}> <button onClick={turncompose}> <i className="fa fa-pencil-square-o"/>Compose</button> ......
上面({})里的参数,基本都是须要外层的容器组件传给它的。
再看一下vsidebar.jsx里(截取部分):
const mapStateToProps = (state) => { return { currentSection: state.currentSection, unreadcount: countunread(state.mails), trashcount: counttrash(state.mails), sentcount: countsent(state.mails) } } const mapDispatchToProps = (dispatch,ownProps) =>{ return { turncompose: () => { dispatch({type: 'TURN_COMPOSE'}) }, handleCategory: (tag) =>{ dispatch({type: 'SELECT_TAG', tag: tag}) } } } const VSidebar = connect(mapStateToProps,mapDispatchToProps)(Sidebar) export default VSidebar
也就是把须要的state用mapStateToProps传递,把方法用mapDispatchToProps来传递便可~写法就是return键值对~
记录一下此次遇到的奇形怪状的bug,都是很是细节的地方:
一、须要引用的组件必定要写export!!须要引用的组件必定要写export!!须要引用的组件必定要写export!! 重要的话说三遍。我被这个疏忽折磨了一个晚上。?
二、若是要用ref取到input里的value的话,这个被命名的input必定别忘记先定义一下变量,举个例子:
<input type = 'text' ref={(v)=>towhom = v} placeholder = 'address'/>
若是只是这么写,在其余地方用towhom.value或者其余towhom的方法就会报错。
得先写一行:
let towhom;
三、写mapDispatchToProps的时候,必定别忘记dispatch外层包个大括号~
const mapDispatchToProps = (dispatch) => { return { handlecompose: (address,message,subject) => {dispatch({ type:'COMPOSE', from: 'Chen Yisha', address:address, time: timeFormat(new Date()), message:message, subject: subject, tag:'sent', read:true })}, deleteemail: (mails,id,tag)=> {dispatch({type: 'DELETE_MAIL',mails,id,tag})} } }
四、若是UI组件用函数的方法写,须要两个及以上的参数的时候要加大括号:
//若是只有一个参数: const ComposePart = (display) => {...} //若是有两个以上参数(别忘记这个大括号): const ComposePart = ({display, handleCompose}) => {...}
好啦,功能部分基本完成以后只要加上样式表就ok~
我此次用的css-module,能够解决全局classname混乱的问题,能够参考下:
https://segmentfault.com/n/13...
其实css仍是很强大的,除了解决应用漂不漂亮的问题,还可使用className完成一些逻辑层面的东西。
好比我在多个组件里都用到了style={{display:display}}
。后面的display是个参数,能够用mapStateToProps传进去,或者依靠其余的state判断一下。而后只要用一个三元运算符就能够解决要不要这个组件显示的问题。
好比:
// 在vcomposepart.jsx里,须要依靠composeORnot这个reducer判断用户有没有选择'comopse’,若是选择了就显示composepart,没有选择就显示mailList和mailDetail这两个组件。 // vcomposepart.jsx const mapStateToProps = (state) => { return { display: state.composeORnot? 'block':'none' } } //composepart.jsx const ComposePart = ({display, handleCompose}) => { let towhom, subject, mailbody return( <div className ={styles.composepart} style={{display:display}}> ......
而后还有一些小技巧:
1)想要有投影一边的box-shadow,能够用:after(用在选择某个sidebar的tag的时候)
.currentSection{ border-left: 5px solid $green; &:after{ content: ''; background:linear-gradient(90deg, $light-green, #fff); width:30px; height:40px; display: block; position: relative; left:-30px; top:-40px; z-index:-1; } }
2) 在整个应用外部用一个box-shadow, 我以为会显得精致漂亮不少~
.mailbox{ position:absolute; top:50%; left:50%; transform: translate(-50%,-50%);//这三行可让整个应用居中 height: auto; width:auto; background-color: #fff; box-shadow: 0 0 20px #eee; //注意这行 border-radius: 5px; }
3)若是须要几个组件在x轴或者y轴上排齐,能够在父级上使用flexbox:
.flexb{ display: flex; display: -webkit-flex; flex-direction:row; flex-wrap:nowrap; justify-content:center; height: 500px; }
基本就是这样啦!还有什么问题你们能够看下个人源码~ 谢谢支持~!
https://github.com/yisha0307/...