这几天用react+redux+webpack写了一个简朴的邮箱,这是我第一次用redux写运用,以为很有必要纪录一下碰到的种种坑~~??
DEMO在这里:
https://yisha0307.github.io/M…
这边是源码:
https://github.com/yisha0307/…
(列位用github的旁友途经请随便帮我点个赞哟!感谢~)
表面如图:
零:最先之前
1、webpack:
webpack基础的设置环境能够先看我这篇文章:
https://segmentfault.com/a/11…
然后再来讲讲此次迥殊的处所。
1)CDN
斟酌到末了bundle.js的大小题目,第三方库我都用的cdn, 此次用到的有:
react / redux / react-dom / react-redux / font-awesome,
除了末了一个,其他的四个都要在webpack.config.js里用externals说明一下:
//webpack.config.js
externals: {
"react":"React",
"redux":"Redux",
"react-dom" :"ReactDOM",
"react-redux":"ReactRedux"
}
然后在写的js/jsx文件里开首援用一下就行:
//相似如许的花样:
import React,{Component} from 'react'
font-awesome由于是css,原本就是全局的,所以就不须要externals,直接用就好了~
2)UglifyJsPlugin
这个plugin也是为了削减末了的bundle.js的~
不过由于装了这个plugin以后热加载的速率会变慢,所以发起开辟的时刻先不要用~
别的另有一些要领能够让bundle.js变小,比方关掉devtool之类的,详细能够看我之前写的一篇笔记:
https://segmentfault.com/n/13…
2、React和Redux
此次全部的运用我是这么部署的:
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'
//1、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
}
}
//2、currentSection
//显现在mailist里的mails
const currentSection = (state = 'inbox', action) => {
switch(action.type){
case 'SELECT_TAG':
return action.tag;
default:
return state
}
}
//3、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
}
}
//4、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
}
}
//5、新加一个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这个库来衔接的,固然也能够挑选不必react-redux,直接在root组件上把一切的action都dispatch一下,然后一级一级传下去。
react-redux这个库也很好明白,主要运用了Provider和connect两个要领.
1)Provider
运用provider就是让一切的组件有取到redux里保留的state的可能性。只需在root组件表面包一层就能够。须要的属性就是store。
//index.jsx
const store = createStore(inboxApp)
class App extends Component{
render(){
return(
<Provider store={store}>
<Mailbox />
</Provider>)
}
}
2)connect()
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,都是异常细节的处所:
1、须要援用的组件肯定要写export!!须要援用的组件肯定要写export!!须要援用的组件肯定要写export!! 主要的话说三遍。我被这个忽视折磨了一个晚上。?
2、假如要用ref取到input里的value的话,这个被定名的input肯定别忘记先定义一下变量,举个例子:
<input type = 'text' ref={(v)=>towhom = v} placeholder = 'address'/>
假如只是这么写,在其他处所用towhom.value或许其他towhom的要领就会报错。
得先写一行:
let towhom;
3、写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})}
}
}
4、假如UI组件用函数的要领写,须要两个及以上的参数的时刻要加大括号:
//假如只要一个参数:
const ComposePart = (display) => {...}
//假如有两个以上参数(别忘记这个大括号):
const ComposePart = ({display, handleCompose}) => {...}
四、css部份
好啦,功用部份基础完成以后只需加上样式表就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/…