感谢无私开源的程序员们~~~代码因为你们更加美腻~

//根index.js
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import './index.js';
import App from './App';
import { GlobalStyle, GithubMarkdownCss, Icon } from './style.js' // 添加全局样式
import Toast from 'react-toast-mobile';
import 'react-toast-mobile/lib/react-toast-mobile.css';
import * as serviceWorker from './serviceWorker'; const Apps = () => {
return (
<Fragment>
<Toast />
<GlobalStyle />
<Icon/>
<GithubMarkdownCss />
<App />
</Fragment>
)
} ReactDOM.render(<Apps />, document.getElementById('root')); // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
//app.js
import React, { Component, Fragment } from 'react';
//引入redux
import { Provider } from 'react-redux'
//store
import store from './store'
import { BrowserRouter, Route, Switch } from 'react-router-dom'; // BrowserRouter HashRouter
//对应一些页面
import Topic from './pages/topic'
import Detail from './pages/detail'
import User from './pages/user'
import Login from './common/login'
import Create from './pages/create'
import Mine from './pages/mine'
import Message from './pages/message'
import ErrorPage from './common/errorPage'
import Auth from './common/auth' class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Fragment>
<Switch>
<Route path="/" exact component={Topic}></Route>
<Route path="/detail/:id" component={Detail}></Route>
<Route path="/user/:id" component={User}></Route>
<Route path="/login" component={Login}></Route>
<Auth path="/create" component={Create}></Auth>
<Auth path="/mine" component={Mine}></Auth>
<Auth path="/message" component={Message}></Auth>
<Route path="/404" exact component={ErrorPage}/>
<Route path="*" component={ ErrorPage } />
</Switch>
</Fragment>
</BrowserRouter>
</Provider >
);
}
} export default App;

封装的时间

//index.js
export function formatDate(str) {
var date = new Date(str);
var time = new Date().getTime() - date.getTime(); //现在的时间-传入的时间 = 相差的时间(单位 = 毫秒)
if (time < 0) {
return '';
} else if (time / 1000 < 60) {
return '刚刚';
} else if ((time / 60000) < 60) {
return parseInt((time / 60000)) + ' 分钟前';
} else if ((time / 3600000) < 24) {
return parseInt(time / 3600000) + ' 小时前';
} else if ((time / 86400000) < 31) {
return parseInt(time / 86400000) + ' 天前';
} else if ((time / 2592000000) < 12) {
return parseInt(time / 2592000000) + ' 月前';
} else {
return parseInt(time / 31536000000) + ' 年前';
}
}
//http.js
import axios from 'axios';
import qs from "qs";
import { T } from 'react-toast-mobile'; // axios 配置
axios.defaults.timeout = 10000;
axios.defaults.baseURL = 'https://cnodejs.org/api/v1' // http request 拦截器
axios.interceptors.request.use(config => {
T.loading()
let user = localStorage.user
if (config.method === 'post') {
config.data = qs.stringify(config.data)
if (user) {
config.data = config.data + `&accesstoken=${JSON.parse(user).accesstoken}`
}
}
if (config.method === 'get') {
if (user) {
config.params = Object.assign(config.params, { accesstoken: JSON.parse(user).accesstoken })
}
}
return config
},
err => {
return Promise.reject(err);
}); // http response 拦截器
axios.interceptors.response.use(response => {
T.loaded()
return response;
},
error => {
T.loaded()
return Promise.reject(error)
}); export default axios;

redux用的是

redux-thunk

//index.js
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk' // 默认action只能是对象,thunk能让action是一个函数 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
)) export default store
//reducer.js
import { combineReducers } from 'redux-immutable'
import { reducer as header } from './../pages/header/store'
import { reducer as topic } from './../pages/topic/store'
import { reducer as detail } from './../pages/detail/store'
import { reducer as user } from './../pages/user/store'
import { reducer as login } from './../common/login/store'
import { reducer as create } from './../pages/create/store'
import { reducer as message } from './../pages/message/store'
import { reducer as replies } from './../pages/replies/store' const reducer = combineReducers({
header,
topic,
detail,
user,
login,
create,
message,
replies
}) export default reducer

store的用法,每一处是在对于的组件页面中使用的,没有抽出来

//reducer.js
import { actionTypes } from './index'
import { fromJS } from 'immutable' // '全部','精华','分享','问答','招聘'
const defaultState = fromJS({
navList: [
{
type: 'all',
text: '全部'
},
{
type: 'good',
text: '精华'
},
{
type: 'share',
text: '分享'
},
{
type: 'ask',
text: '问答'
},
{
type: 'job',
text: '招聘'
},
],
tab: 'all',
}) const reducer = (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_TAB:
return state.set('tab', action.data)
default:
return state
}
} export default reducer
//header/index.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { HeaderWrapper, NavList, NavItem } from "./style";
import { actionCreators } from "./store";
import { actionCreators as topicActionCreators } from "./../topic/store";
import { Link } from "react-router-dom"; class Header extends Component {
render() {
let { navList, changeTab } = this.props;
let newNavList = navList.toJS();
return (
<HeaderWrapper>
<NavList>
{newNavList.map(it => {
return (
<NavItem key={it.type} onClick={() => changeTab(it.type)}>
<Link to={"/?tab=" + it.type}>{it.text}</Link>
</NavItem>
);
})}
</NavList>
</HeaderWrapper>
);
}
} const mapState = state => {
return {
navList: state.getIn(["header", "navList"])
};
}; const mapDispatch = dispatch => {
return {
changeTab(type) {
// let page = 1, limit = 15
dispatch(topicActionCreators.clearTopicList([]));
dispatch(topicActionCreators.changePage(1));
dispatch(actionCreators.changeTab(type));
// dispatch(topicActionCreators.getTopic(page, limit, type))
}
};
}; export default connect(
mapState,
mapDispatch
)(Header);

//index.js
import React, { Component } from "react";
import { connect } from "react-redux";
import Footer from "./../../common/footer";
import TopNav from "./../../common/topnav";
import {
MessageWrapper,
MessageList,
MessageItem,
MessageItemLeft,
MessageItemRight,
MessageNothing
} from "./style";
import { actionCreators } from "./store";
import { formatDate } from "./../../utils";
import { Link } from "react-router-dom"; class Message extends Component {
render() {
let { messageList } = this.props;
let newMessageList = messageList.toJS();
if (JSON.stringify(newMessageList) === "{}") return null;
return (
<MessageWrapper>
<TopNav title={"消息"} />
{newMessageList.has_read_messages.length ||
newMessageList.hasnot_read_messages.length ? (
<MessageList>
{newMessageList.hasnot_read_messages.map((it, index) => {
return (
<MessageItem key={index}>
<MessageItemLeft>
<Link to={"/user/" + it.author.loginname}>
<img src={it.author.avatar_url} alt="" />
</Link>
</MessageItemLeft>
<MessageItemRight>
<div className="item-hd">
<span className="name">
<Link to={"/user/" + it.author.loginname}>
{it.author.loginname}
</Link>
</span>
<span className="time">
{formatDate(it.reply.create_at)}
</span>
</div>
<div className="item-bd">
在话题{" "}
<Link to={"/detail/" + it.topic.id}>
{it.topic.title}
</Link>
回复了你
</div>
</MessageItemRight>
</MessageItem>
);
})}
{newMessageList.has_read_messages.map((it, index) => {
return (
<MessageItem key={index}>
<MessageItemLeft>
<Link to={"/user/" + it.author.loginname}>
<img src={it.author.avatar_url} alt="" />
</Link>
</MessageItemLeft>
<MessageItemRight>
<div className="item-hd">
<span className="name">
<Link to={"/user/" + it.author.loginname}>
{it.author.loginname}
</Link>
</span>
<span className="time">
{formatDate(it.reply.create_at)}
</span>
</div>
<div className="item-bd">
回复了你的话题{" "}
<Link to={"/detail/" + it.topic.id}>
{it.topic.title}
</Link>
</div>
</MessageItemRight>
</MessageItem>
);
})}
</MessageList>
) : (
<MessageNothing>暂无消息</MessageNothing>
)}
<Footer />
</MessageWrapper>
);
}
componentDidMount() {
let loginState = localStorage.user;
if (this.props.isLogined || loginState) {
this.props.getMessage();
}
}
} const mapState = state => {
return {
messageList: state.getIn(["message", "messageList"]),
isLogined: state.getIn(["login", "isLogined"])
};
}; const mapDispatch = dispatch => {
return {
getMessage() {
dispatch(actionCreators.getMessageCount());
}
};
}; export default connect(
mapState,
mapDispatch
)(Message);
//src/pages/replies/index.js
import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
import { Link } from "react-router-dom";
import { RepliesWrapper, RepliesTextarea, RepliesButton } from "./style";
import { T } from "react-toast-mobile"; class Replies extends PureComponent {
render() {
let { handleConfirm, id, replyId, author } = this.props;
return (
<RepliesWrapper>
{localStorage.user ? (
<Fragment>
<RepliesTextarea
ref={textarea => {
this.content = textarea;
}}
placeholder={author ? "@" + author : "请输入回复内容"}
/>
<RepliesButton
onClick={() => {
handleConfirm(id, replyId, author, this.content);
}}
>
回复
</RepliesButton>
</Fragment>
) : (
<div className="login">
你丫的先<Link to={"/login"}> 登录</Link> 才能发评论
</div>
)}
</RepliesWrapper>
);
}
} const mapDispatch = dispatch => {
return {
handleConfirm(id, replyId, author, content) {
if (content.value.length) {
if (replyId !== "") {
dispatch(
actionCreators.sendReplies(
id,
replyId,
`[@${author}](/user/${author}) ${content.value}`
)
);
} else {
dispatch(actionCreators.sendReplies(id, replyId, content.value));
}
content.value = "";
} else {
T.notify("回复内容不能为空");
}
}
};
}; export default connect(
null,
mapDispatch
)(Replies);
//src/common/footer/index.js
import React from "react";
import { NavLink as Link } from "react-router-dom";
import { FooterWrapper, FooterItem } from "./style"; const Footer = () => {
return (
<FooterWrapper>
<FooterItem>
<Link to={"/"} exact>
<i className="iconfont"></i>
<p>首页</p>
</Link>
</FooterItem>
<FooterItem>
<Link to={"/create"}>
<i className="iconfont"></i>
<p>发表</p>
</Link>
</FooterItem>
<FooterItem>
<Link to={"/message"}>
<i className="iconfont"></i>
<p>消息</p>
</Link>
</FooterItem>
<FooterItem>
<Link to={"/mine"}>
<i className="iconfont"></i>
<p>我的</p>
</Link>
</FooterItem>
</FooterWrapper>
);
};
export default Footer;

//src/common/loading/index.js
import React from "react";
import { Spinner, BounceTop, BounceBottom } from "./style"; const Loading = () => {
return (
<Spinner>
<BounceTop />
<BounceBottom />
</Spinner>
);
};
export default Loading;
//src/common/login/index.js
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
import { Redirect } from "react-router-dom";
import { LoginWrapper, Input, Button, LoginBack } from "./style";
import TopNav from "./../topnav"; class Login extends PureComponent {
render() {
let { isLogined, path } = this.props;
let from = path ? { pathname: path } : { pathname: "/" };
console.log('...from',from);
if (isLogined) return <Redirect to={from} />;
return (
<LoginBack>
<TopNav title={"登录"} />
<LoginWrapper>
<Input
placeholder="accessToken"
ref={input => {
this.username = input;
}}
/>
<Button
onClick={() => {
this.props.login(this.username);
}}
>
登录
</Button>
</LoginWrapper>
</LoginBack>
);
}
} const mapState = state => {
return {
isLogined: state.getIn(["login", "isLogined"]),
path: state.getIn(["login", "path"])
};
}; const mapDispatch = dispatch => {
return {
login(usernameElem) {
dispatch(actionCreators.login(usernameElem.value));
}
};
}; export default connect(
mapState,
mapDispatch
)(Login);
//src/common/topnav/index.js
import React, { PureComponent, Fragment } from "react";
import { TopNavWarpper, Back } from "./style";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { actionCreators as loginActionCreators } from "./../login/store"; class TopNav extends PureComponent {
render() {
let loginState = localStorage.user;
return (
<Fragment>
<TopNavWarpper>
<Back onClick={() => this.goBack()}>
<i className="iconfont"></i>
</Back>
{this.props.match.path === "/mine" ? (
<span>个人中心</span>
) : (
<span>{this.props.title}</span>
)}
{this.props.match.path === "/mine" && loginState ? (
<span onClick={() => this.quite()}>
<i className="iconfont"></i>
</span>
) : (
<span />
)}
</TopNavWarpper>
</Fragment>
);
} goBack() {
this.props.history.goBack();
}
quite() {
localStorage.user = "";
this.props.history.push("/");
this.props.logout();
}
} const mapDispatch = dispatch => {
return {
logout() {
dispatch(loginActionCreators.isLogined(false));
}
};
}; export default connect(
null,
mapDispatch
)(withRouter(TopNav));

//src/pages/header/store/reducer.js
//根据type值加载数据
import { actionTypes } from './index'
import { fromJS } from 'immutable' // '全部','精华','分享','问答','招聘'
const defaultState = fromJS({
navList: [
{
type: 'all',
text: '全部'
},
{
type: 'good',
text: '精华'
},
{
type: 'share',
text: '分享'
},
{
type: 'ask',
text: '问答'
},
{
type: 'job',
text: '招聘'
},
],
tab: 'all',
}) const reducer = (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_TAB:
return state.set('tab', action.data)
default:
return state
}
} export default reducer

//src/pages/detail/index.js
import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import {
MianWrapper,
MianContent,
MianTitle,
MianInfo,
ReplyWrapper,
ReplyContent,
ReplyList,
ReplyItem
} from "./style";
import { actionCreators } from "./store";
import { formatDate } from "./../../utils";
import { Link } from "react-router-dom";
import TopNav from "./../../common/topnav";
import Replies from "./../replies"; class Detail extends PureComponent {
constructor(props) {
super(props);
this.state = {
currIndex: -1,
tab: {
good: "精华",
share: "分享",
ask: "问答",
job: "招聘"
}
};
}
render() {
let { topicDetailList } = this.props;
let newList = topicDetailList.toJS();
// 导步加载数据时,newList转为空,render的时候去读一个空对象的属性时会报错,现提供如下解决方案
// 方法一
// let {title="",create_at = "" ,author = "",visit_count = 0 ,replies = [],tab='good',content=''} = newList
// 方法二
// if (JSON.stringify(newList) === "{}") return null;
// 方法三
// 用 && 操作符
return (
<Fragment>
<TopNav title={"详情"} />
<MianWrapper>
<MianTitle>{newList && newList.title}</MianTitle>
<MianInfo>
<span>发布于 {formatDate(newList && newList.create_at)}</span>
<span>
作者 {newList && newList.author && newList.author.loginname}
</span>
<span>阅读 {newList && newList.visit_count}</span>
<span>来自 {this.state.tab[newList && newList.tab]}</span>
</MianInfo>
<MianContent
className="markdown-body"
dangerouslySetInnerHTML={{ __html: newList && newList.content }}
/>
</MianWrapper>
<ReplyWrapper>
<ReplyContent>
全部回复({newList && newList.replies && newList.replies.length})
</ReplyContent>
{newList && newList.replies && newList.replies.length ? (
<ReplyList>
{newList &&
newList.replies &&
newList.replies.map((it, index) => {
return (
<ReplyItem key={index}>
<div className="replyAvuthor">
<Link to={"/user/" + it.author.loginname}>
<img src={it.author.avatar_url} alt="avatar_url" />
</Link>
</div>
<div className="replyContent">
<div className="content-hd">
<p>
<span className="name">
<Link to={"/user/" + it.author.loginname}>
{it.author.loginname}
</Link>
</span>
{formatDate(it.create_at)}
</p>
<p className="r">
<span
className="replies"
onClick={() => this.openReplies(index)}
>
{" "}
<i className="iconfont"></i>{" "}
</span>
<span className="num"># {index + 1}</span>
</p>
</div>
<p
className="markdown-body"
dangerouslySetInnerHTML={{ __html: it.content }}
/>
{this.state.currIndex === index ? (
<Replies
author={it.author.loginname}
id={newList.id}
replyId={it.id}
/>
) : null}
</div>
</ReplyItem>
);
})}
</ReplyList>
) : (
<p className="noReply">暂无回复</p>
)}
</ReplyWrapper>
<Replies id={newList.id} replyId={""} />
</Fragment>
);
}
componentDidMount() {
window.scrollTo(0, 0);
this.props.getTopicDetail(this.props.match.params.id);
} openReplies(index) {
this.setState(() => {
return {
currIndex: index
};
});
}
} const mapState = state => {
return {
topicDetailList: state.getIn(["detail", "topicDetailList"])
};
}; const mapDispatch = dispatch => {
return {
getTopicDetail(id) {
dispatch(actionCreators.getTopicDetail(id));
}
};
}; export default connect(
mapState,
mapDispatch
)(Detail);

感谢作者无私开源的精神感恩~!

react-cnode的更多相关文章

  1. 【原】小写了一个cnode的小程序

    小程序刚出来的第一天,朋友圈被刷屏了,所以趁周末也小玩了一下小程序.其实发觉搭建一个小程序不难,只要给你一个demo,然后自己不断的查看文档,基本就可以入门了,不过对于这种刚出来的东西,还是挺多坑的, ...

  2. React Native专题-江清清

    本React Native讲解专题:主要讲解了React Native开发,由基础环境搭建配置入门,基础,进阶相关讲解. 刚创建的React Native交流8群:533435865  欢迎各位大牛, ...

  3. React Native开源项目案例

    (六).React Native开源项目: 1.Pober Wong_17童鞋为gank.io做的纯React Native项目,开源地址:https://github.com/Bob1993/Rea ...

  4. 基于vue的nuxt框架cnode社区服务端渲染

    nuxt-cnode 基于vue的nuxt框架仿的cnode社区服务端渲染,主要是为了seo优化以及首屏加载速度 线上地址 http://nuxt-cnode.foreversnsd.cngithub ...

  5. React Native 开源项目汇总

    最近闲来无事,学习了React Native开发Android APP,自我感觉RN APP的效果和Native APP比还是蛮不错,以下是找到的一些优秀源码,仅供学习参考... React Nati ...

  6. react组件的生命周期

    写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...

  7. 十分钟介绍mobx与react

    原文地址:https://mobxjs.github.io/mobx/getting-started.html 写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指 ...

  8. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  9. React 入门教程

    React 起源于Facebook内部项目,是一个用来构建用户界面的 javascript 库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React 把每一个组件当成了一个状态机,组件内 ...

  10. 通往全栈工程师的捷径 —— react

    腾讯Bugly特约作者: 左明 首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 React,红色的 ...

随机推荐

  1. Ajax和json一道基本的练习题

    关于ajax是javaEE中最基本的操作: 下面是这道题: 基本功能: jsp+servlet+ajax实现用户信息查询,实现无刷新删除 用户信息包括 学号 姓名 出生日期 性别 操作 2017010 ...

  2. CentOS6.5下源码安装MySQL5.6.35

    接上一篇文章使用RPM包安装MySQL,确实很方便.但是安装后却不知道各文件保存在哪个文件夹下!尝试使用源码安装~本文主要参考:CentOS 6.4下编译安装MySQL 5.6.14一.卸载旧版本 . ...

  3. vscode, eslint, prettier, vetur冲突及解决

    这3工具都必须安装. 但是安装之后, 规则冲突又让人头疼. 讲下解决方案吧.一 从0开始1. 禁止工作区插件, 如下图:  2. 清空用户设置(Code–>首选项–>设置–>[右上角 ...

  4. java mybatis 参数问题

  5. springboot拦截器之验证登录

    添加jar包,这个jar包不是必须的,只是在拦截器里用到了,如果不用的话,完全可以不引入 <dependency> <groupId>org.apache.commons< ...

  6. TZOJ 4267 An Easy Puz(深搜)

    描述 Wddpdh find an interesting mini-game in the BBS of WHU, called “An easy PUZ”. It’s a 6 * 6 chess ...

  7. getBoundingClientRect介绍

    getBoundingClientRect用于获取元素相对与浏览器视口的位置 由于getBoundingClientRect()已经是w3c标准,所以不用担心兼容,不过在ie下还是有所区别 { top ...

  8. spring源码学习之springMVC(一)

    个人感觉<Spring技术内幕:深入解析Spring架构与设计原理(第2版)>这本书对spring的解读要优于<Spring源码深度解析(第2版)>这本书的,后者感觉就是再陈述 ...

  9. windows device recovery tool 刷机

    ch 春节期间,拿出来诺基亚1020拍照,误删软件,无法登陆微软账号,考虑刷机处理 下载windows device recovery tool,进行刷机,但是固件下载一直失败 考虑下载好固件包,ff ...

  10. 2019.7.27 NOIP模拟测试9 反思总结

    先来整理题目 T1题目大意:给出n个数字和一个质数作为模数,一个变量x初始值为1.进行m次操作,每次让x随机乘上n个数中的一个,问m次操作以后x的期望值. 答案一定可以用分数表示,输出分子乘分母逆元的 ...