今天分享一个react应用,应在第一篇作品中说要做一个react+redux+xxx的应用。已经做完一部分,拿出来分享。github地址为:点我就可以咯~
这里实现了一个新闻移动站的spa。本来想写pc端的,但是比较懒,而且因为主要是react的项目,关于css和布局的细节就是糊弄人的了。T.T,这里只说关于这个项目的js部分。
这里的功能很简单,有一下几点:
1,按”心“排序
当比上一个多了,就会排到前面。
2.评论部分
新闻的评论部分类似qq空间的评论
当然,也可以点击新闻回复的哦。
=============================================
大家看到了,功能比较简单,大家clone 项目下来,直接能用的,里面已经传了我用webpack打包好的文件。
=============================================
bundle.js有3m+,这个完全是因为我加了很多库进去,比如轮播图的swiper等等,而且这些东西都是没有压缩过的。你压缩完之后,会觉得react做移动端也可以的。
就不要再问我为什么不用rn做这个。。因为我不会T.T
=============================================
要看懂本文也需要一定的基础,比如es6基础,react和redux基础。
=============================================
最后一点废话,这个东西还没有做完,只是一部分,接下来会用redux的router把它作为一个完全的spa,并且完善它的测试部分。大家期待吧~
=============================================(主文分界线)
好,接下来介绍我们的主角redux,不会的可以转到我的另一篇博文:点此
redux,昨天花了一上午,竟然看完了它全部的源码。跟你们说,它未压缩的redux.js仅有600行。angular已经1w+行了。。。
主要的是很简单,源码一看就懂,而且redux完全是fp,也就是函数式编程,它内置了一些函数式编程的例子。运用fp的库还有lodas和underscore。我将会写另一篇介绍fp与redux的博文~~
分析代码之前,先给出文件目录:
这个目录是官方example的目录,我就直接拿来用了。webpack整理的文件在public中,bootstrap没有在webpack中打包,项目中一般也用不到,其他的css和js文件都打包进去了。
其中,fonts是字体,dist存放了webpack打包后的js和css文件,public里面是轮播图js文件和图片文件。test是测试文件,现在还没写。因为不是tdd。。
剩下的actions存放的是redux的action文件。components存放的是react的组件,containers是redux如何封装组件的配置文件。reducers是redux的reducer文件,store是创建store的配置文件。
webpack主入口是index.js 它里面是添加了一些应用需要的文件,比如import Swiper from "./public/js/swiper-3.3.1.min.js"和import "./public/js/swiper.min.css",还有main.css
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './containers/App'
import configureStore from './store/configureStore'
import "./dist/main.css"; import Swiper from "./public/js/swiper-3.3.1.min.js"
import "./public/js/swiper.min.css" import 'react-bootstrap';
const store = configureStore(); render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
大家会看到这个项目跟上一个redux入门中的项目结构,甚至代码都类似,因为就是从上一个代码的基础上改的,不明白可以看上一篇文章。
主页就不多说了,开始的页面布局和引入css文件和打包之后的js文件。
主要说一下上面提到的功能。
首先是排序。排序的原理是什么?根据心排序,心的多少。
Box是那三个新闻,这里会根据children来循环遍历出来,那个children是啥呢?是redux的store的counter数据排序之后的数据。
可以看到,我自己写了一个数据排序,根据数据的val属性排序,然后遍历,就是完成了心的特性。
state={
counter:{
one:{id:1,counter:0,title:"xxxx" ,time:1},
two:{id:2,counter:0,title:"xxxx", time:1},
three:{id:3,counter:0,title:"xxxx",time:1}
}
}
这里的counter数据,counter是心的数量。排序就不说了,看代码吧。
评论功能呢,有三个难点。1是点击回复弹出input框,2是如何回复当前的评论。比如我回复的评论必须是这个评论,而不是下一个。3就是如何把评论内容显示出来。
最难的就是2了。这个还要设计一下store的数据结构。我用了一个小方法避免了添加id。这个以后再说。
1。点击弹出input框。
写react或者ng应用也不能用jquery的思想写了。点击弹出不能用click事件操作dom了。转变为react的思想,props和state。于是,要这样写。
这是根据state渲染dom阿,然后点击操作state。对了,react有个操作state的方法和初始化state的方法来着。叫getInitialState。
这里有个隐藏的坑,es6的写法不能用getInitialState。于是我就像上面这样写。es6怎么写state呢。于是我在下面的组件里又这样写:
这个state成为了这个组件的一个属性,但是确实没法用getInitialState了。不过可以用 this.setState({display: false})。这样写。来操作state
2,这个比较麻烦,首先看一下我设计的评论的store:
var data=[
[
{
text:"这里是评论1",
huifu:["huifuxxxxxxxxxxxxx",'2xxxxxxxxxxxxxxxxxxxx','3xxxxxxxxxxxxxxxxx']
},
{
text:"这里是评论1.2",
huifu:["huifuxxxxxxxxxxxxx"]
}
],[
{
text:"这里是评论2",
huifu:["huifuxxxxxxxxxxxxx",'2xxxxxxxxxxxxxxxxxxxx','3xxxxxxxxxxxxxxxxx']
}
],[
{
text:"这里是评论3",
huifu:["huifuxxxxxxxxxxxxx",'2xxxxxxxxxxxxxxxxxxxx','3xxxxxxxxxxxxxxxxx']
}
]
]
这是整个评论的部分,包括回复。如果你要添加评论就在data[文章id].push(一样结构的评论数据),回复也是直接添加数据。
问题来了,那我们怎么保证这个回复是这个评论的呢,比如我回复123,你怎么知道是评论1的还是评论1.2(代码中的评论1.2)的呢?
这里有个小方法。我的评论框是独立于遍历之外的,就是说,你不能给他传一个文章id。那怎么办?
它取本组件的state。然后评论操作state。就是这样。评论在打开一个回复input的时候,传入本评论的id,改变本组件的state,然后就不管它的事了。
这里的小方法避免了每个评论都加一个id属性,而是一个评论数组,用的就是数组map方法的keys--
keys,是数组的键,也就是0,1,2,3用这个当作文章id。可以的!
注意这里你循环出子组件的时候,必须给他一个key的props,由于react的diff算法,必须保证子组件的位置和渲染正确,传一个key,那正好,没有id我就用这里的keys传了:
这样就没问题了。
3。显示的问题,就是上面的遍历的问题了,输入框的显示就是组件自身state的问题了。
看组件是如何显示:
{
data[newId].map(function (items,keys) {
return (
<div key={keys} className="body-text">
<h5>游客: <a onClick={that.handleClick.bind(that,keys)}>回复</a> <a href="">转发</a></h5>
<p>{items.text}</p>
{
items.huifu.map(function (item,keyss) {
return ( <div key={keyss}>
<h5 className="huifu-user">游客回复:<a onClick={that.handleClick.bind(that,keys)} >回复</a> <a href="">转发</a></h5>
<p className="huifu">{item}</p>
</div>
)
})
} </div>
)
})
}
data数组是我们的数据,上面给了格式。循环遍历。
主要的是回复框的显示,操作state,这里给了他一个display属性,但是他不是true和false,而是false和keys,这个keys就是当前评论的id,这个id成为遍历的keys属性。
1的时候那张图就是根据display显示,这就不多说了。
react的优势:
其实这么多文件夹,这么多js文件,用js写,逻辑又清楚又快,而用react写还带有学习成本。为啥用react写。抛开赶前端潮流不说。react的优势在于维护起来爽翻天。
js写这些逻辑刚开始写完是很清晰,我觉得过一段时间以后,想维护就很难了吧。还有一点,我想改数据的时候直接修改state,想加一个新闻就复用我的组件,根据react的虚拟dom的算法,也是不慢的。
用redux的优势就是等这个新闻项目做大,一堆一堆的js代码,如果有redux维护那就简单多了。因为纯函数的关系,redux的测试也是非常好写的。redux压缩前600行代码,加上react也是轻量级框架,这点也是很受人好评的。
用这些框架都是类似,都是数据流,他们让你用数据控制页面。ng也是如此,操作仅仅是修改数据,而不会频繁的操作dom。这是框架的优点。react在此基础上做到的低耦合,单项数据流。经常听到有react维护比较爽的呼声。-。-好维护,低耦合,轻量级。正好符合现在前端对框架的要求。于是好多人选择了react。还有JSX的写法也是比较爽的0.0react背后还有facebook撑腰。嗯,必火。。。
关于本新闻端的问题:
问题在于这里:三篇新闻,每一篇当作一个事件来处理,于是--
export const LOVE_COUNTER_ONE = 'LOVE_COUNTER_ONE'
export const LOVE_COUNTER_TWO = 'LOVE_COUNTER_TWO'
export const LOVE_COUNTER_THREE = 'LOVE_COUNTER_THREE' export const TXST_COUNTER_ONE = 'TXST_COUNTER_ONE'
export const TXST_COUNTER_TWO = 'TXST_COUNTER_TWO'
export const TXST_COUNTER_THREE = 'TXST_COUNTER_THREE' export const HF_COUNTER_ONE = 'HF_COUNTER_ONE'
export const HF_COUNTER_TWO = 'HF_COUNTER_TWO'
export const HF_COUNTER_THREE = 'HF_COUNTER_THREE'
有人会说,如果100篇新闻,岂不是写n多的action?
当初在写的时候,想的是作为新闻的主页,我只放3个新闻,后面加一个更多,点开打开新闻列表页。当然,如果把这个作为新闻列表页那确实要写n多的xxx。。
那当这个新闻端改为新闻列表页怎么写呢?
首先构建一个store,把新闻id,新闻内容写进去。当然也可以组合它的评论和回复,不组合,通过id关联也是可以的。。(怎么感觉像数据库了0.0)
那这里的action就变了,改为TXST_COUNTER_NEWS,当然,这个action需要传文章的id。这个不麻烦。
reducer这里就根据文章id操作store,如果不组合评论和回复,那么,这个新闻store只需要删减新闻内容。注意新闻删了,对应的评论和回复也要删除。
组合评论和回复呢,那么也是同样,评论和回复的内容留给游客们去添加。
组件里就注意了,因为新闻组合了,那么上面就需要传一个新闻id,智能组件根据id传方法与数据给呆滞组件。。原理是这样。大家可以试着修改一下,自己动手能学到很多东西。
还有就是webpack打包文件太大怎么办。我这个文件3m,本地加载都要143ms,这段时间3张下载完的图片并排,很丑。。
这里的减少体积大小的方法就是关闭source map,devTool也关了。重复的文件打包成common.js,还有就是压缩文件- -,当然这些都是生成环境下了。
这里测试了一下(未传到github),未压缩前可以弄到864k,当然公共库,比如Swiper可以用cdn,压缩完后这样一个数量级的应用就可以用在移动端了。
这里的操作是-删除没用的react-bootstrap,删除重复的style-loader,删除source-map及webpack hot replace及一些webpack常用插件。
还有一点,把文件作为chunk按需加载,这个貌似这个场景下还是不需要的。
需要注意的就这么多,接下来会添加路由功能,使他真正的成为spa的应用。当然,大神的标志之一---会不会写测试也会一并补齐。敬请期待~