资讯专栏INFORMATION COLUMN

一个简单邮箱应用的实现:使用react+redux+webpack+css-modules

luffyZh / 2117人阅读

摘要:这个库也很好理解,主要使用了和两个方法使用就是让所有的组件有取到里保存的的可能性。我被这个疏忽折磨了一个晚上。后面的是个参数,可以用传进去,或者依靠其他的判断一下。然后只要用一个三元运算符就可以解决要不要这个组件显示的问题。

这几天用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引入到里。

一、实现功能

先看一眼我这次的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(
        
            
        )
    }
}
2)connect()

connect就是一个比较重要的方法啦,它的意思就是把容器组件和UI组件联系在一起。这样只要写好UI组件,外面用connect()包一层就行啦~
这个应用的UI组件和容器组件是这样的:

前面加大v的就是容器组件,基本都是一一对应的关系,当然有些不需要逻辑层的可以只用UI组件就行,比如MailItem这个~

拿Sidebar举个例子(截取部分):

//sidebar.jsx
const Sidebar =({currentSection, unreadcount,trashcount,sentcount,handleCategory,turncompose}) => {
    return (
        
......

上面({})里的参数,基本都是需要外层的容器组件传给它的。
再看一下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一定别忘记先定义一下变量,举个例子:

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(
    
......

然后还有一些小技巧:

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/...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81754.html

相关文章

  • React技术栈实现XXX点评App demo

    摘要:项目的架构也是最近在各种探讨研究。还求大神多指点项目技术总结技术栈项目结构探究初体验关于项目中的配置说明项目简单说明开发这一套,我个人的理解是体现的是代码分层职责分离的编程思想逻辑与视图严格区分。前端依旧使用技术栈完成。 项目地址:https://github.com/Nealyang/R...技术栈:react、react-router4.x 、 react-redux 、 webp...

    wslongchen 评论0 收藏0
  • 从零开始编写React-Express单页博客应用(学习总结)

    摘要:单页博客应用编写总结很久之前就想写一个博客应用在一开始想要直接用和模板直接写但是暑假一开始的时候不小心入了的坑所以就一不做二不休直接用写那既然用了不写个单页应用也过意不去了不前前后后写了将近两个星期现在看来这其实是一个很容易的应用但是鉴于 React-Express单页博客应用编写总结 很久之前就想写一个博客应用.在一开始想要直接用express和ejs模板直接写, 但是暑假一开始的时...

    Jioby 评论0 收藏0
  • 指尖前端重构(React)技术调研分析

    摘要:一为什么选择是当前前端应用最广泛的框架。目前来看的生态系统要比大的多,在等最大的技术社区搜索两者,的搜索结果是的十倍左右,另外据近期统计使用的站点是的几百倍以上。其中是基于技术,依然是浏览器应用。 一、为什么选择React React是当前前端应用最广泛的框架。三大SPA框架 Angular、React、Vue比较。 Angular出现最早,但其在原理上并没有React创新的性能优化...

    AlphaWallet 评论0 收藏0
  • 利用react-redux-tpl快速开发react-redux-webpack项目

    摘要:将它开源出来,希望能给某些同学带来帮助,如果有设计的不好的地方,也希望能及时指出,共同进步。和为组件,下的文件一般为直接配合路由使用的包装组件,为具体业务组件,这个地方有的规范将组件分为三层,我个人认为这个必要性不大。 个人学习使用react已经有一段时间了,虽然没有在真正的产品项目中大规模使用react,但是在自己的博客、小网站、一些开源项目中已经使用过好几次了,使用react创建项...

    X1nFLY 评论0 收藏0
  • 【单页面博客从前端到后端】基于 DVA+ANTD 搭建博客前后台界面

    摘要:在的的配置中添加自定义主题由脚手架和官网介绍,我们已经自己配置并新建好了主题文件。单页面博客从前端到后端环境搭建单页面博客从前端到后端基于搭建博客前后台界面单页面博客从前端到后端基于和的权限验证与的设计 在上篇文章我们已经搭建好了基础的开发环境,接下来会介绍如何引入 DVA 和 ANTD ,以及在引入过程中需要注意的问题。这里只会详细的书写部分组件,其他的组件都是大同小异。你可以在 g...

    zqhxuyuan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<