手把手教你用React实现一个简单的个人博客
学习 React 的过程中实现了一个个人主页,没有复杂的实现和操作,适合入门 ~ 原文地址:https://github.com/axuebin/react-blog/issues/17 这个项目其实功能很简单,就是常见的主页、博客、demo、关于我等功能。 页面样式都是自己写的,黑白风格,可能有点丑。不过还是最低级的 CSS ,准备到时候重构 ~ 如果有更好的方法,或者是我的想法有偏差的,欢迎大家交流指正 欢迎参观:http://axuebin.com/react-blog Github:https://github.com/axuebin/react-blog 预览图首页
博客页
文章内容页
Demo页
关键技术
准备工作由于不是使用 React 脚手架生成的项目,所以每个东西都是自己手动配置的。。。 模块打包器打包用的是 官方文档:https://webpack.js.org/ 中文文档:https://doc.webpack-china.org/ 对于 var webpack = require('webpack'); var path = require('path'); module.exports = { context: __dirname + '/src',entry: "./js/index.js",module: { loaders: [ { test: /.js?$/,exclude: /(node_modules)/,loader: 'babel-loader',query: { presets: ['react','es2015'] } },{ test: /.css$/,loader: 'style-loader!css-loader' },{ test: /.js$/,loader: 'eslint-loader' },{ test: /.json$/,loader: 'json-loader' } ] },output: { path: __dirname + "/src/",filename: "bundle.js" } }
包管理包管理现在使用的还是 官方文档:https://docs.npmjs.com/
关于
代码检查项目使用现在比较流行的 ESLint:https://github.com/eslint/eslint eslint-config-airbnb:https://www.npmjs.com/package/eslint-config-airbnb 在 使用
module: { loaders: [ { test: /.js$/,loader: 'eslint-loader' } ] }
{ "extends": "airbnb","env":{ "browser": true },"rules":{} } 然后在运行 这里有常见的ESLint规则:http://eslint.cn/docs/rules/ 数据源由于是为了练习 API在这:https://developer.github.com/v3/issues/ 我也没看完全部的API,就看了看怎么获取 https://api.github.com/repos/axuebin/react-blog/issues?creator=axuebin&labels=blog 通过控制参数 // 为了方便,我把注释写在json中了。。 [{ "url":,// issue 的 url "id":,// issue id , 是一个随机生成的不重复的数字串 "number":,// issue number , 根据创建 issue 的顺序从1开始累加 "title":,// issue 的标题 "labels": [],// issue 的所有 label,它是一个数组 "created_at":,// 创建 issue 的时间 "updated_at":,// 最后修改 issue 的时间 "body":,// issue 的内容 }] 异步请求数据项目中使用的异步请求数据的方法时 关于 使用起来很简单: fetch(url).then(response => response.json()) .then(json => console.log(json)) .catch(e => console.log(e)); markdown 渲染在
使用起来都很简单。 如果是 import ReactMarkdown from 'react-markdown'; const input = '# This is a headernnAnd this is a paragraph'; ReactDOM.render( <ReactMarkdown source={input} />,document.getElementById('container') ); 如果是 import marked from 'marked'; const input = '# This is a headernnAnd this is a paragraph'; const output = marked(input); 这里有点不太一样,我们获取到了一个字符串 <div dangerouslySetInnerHTML={{ __html: output }} /> 由于我们的项目是基于 代码高亮代码高亮用的是 它和 只需要这样既可: import hljs from 'highlight.js'; marked.setOptions({ highlight: code => hljs.highlightAuto(code).value,});
@import '~highlight.js/styles/atom-one-dark.css'; 在这可以看到每种语言的高亮效果和配色风格:https://highlightjs.org/ Reactstate 和 props 是什么可以看之前的一篇文章:https://github.com/axuebin/react-blog/issues/8 关于React组件的生命周期可以看之前的一篇文章:https://github.com/axuebin/react-blog/issues/9 前端路由项目中前端路由用的是 官方文档:https://reacttraining.com/react-router/web/guides/quick-start 中文文档:http://reacttraining.cn/ 基本使用<Link to="/blog">Blog</Link> <Router> <Route exact path="/" component={Home} /> <Route path="/blog" component={Blog} /> <Route path="/demo" component={Demo} /> </Router> 注意:一定要在根目录的 2级目录跳转比如我现在要在博客页面上点击跳转,此时的 <Route path={`${this.props.match.url}/article/:number`} component={Article} /> 这样就可以跳转到 HashRouter当我把项目托管到
通过了解,知道了原因是这样,并且可以解决:
路由跳转后不会自动回到顶部当前一个页面滚动到一定区域后,点击跳转后,页面虽然跳转了,但是会停留在滚动的区域,不会自动回到页面顶部。 可以通过这样来解决: componentDidMount() { this.node.scrollIntoView(); } render() { return ( <div ref={node => this.node = node} ></div> ); } 状态管理项目中多次需要用到从 官方文档:http://redux.js.org/ 中文文档:http://cn.redux.js.org/ 简单的来说,每一次的修改状态都需要触发 关于状态管理这一块,由于还不是太了解,就不误人子弟了~ 主要组件React是基于组件构建的,所以在搭建页面的开始,我们要先考虑一下我们需要一些什么样的组件,这些组件之间有什么关系,哪些组件是可以复用的等等等。 首页
可以看到,我主要将首页分成了四个部分:
博客页
博客页就是很中规中矩的一个页面吧,这部分是整个项目中代码量最多的部分,包括以下几部分:
文章列表文章列表其实就是一个 <div class="archive-list"> <div class="blog-article-item">文章1</div> <div class="blog-article-item">文章2</div> <div> 对于每一个
一个文章item组件它可能需要包括:
如果用 <div class="blog-article-item"> <div class="blog-article-item-title">文章标题</div> <div class="blog-article-item-time">时间</div> <div class="blog-article-item-label">类别</div> <div class="blog-article-item-label">标签</div> <div class="blog-article-item-desc">摘要</div> </div> 所以,我们可以有很多个组件:
它们可能是这样一个关系: <ArticleList> <ArticleItem> <ArticleTitle /> <ArticleTime /> <ArticleLabel /> <ArticleDesc /> </ArticleItem> <ArticleItem></ArticleItem> <ArticleItem></ArticleItem> </ArticleList> 分页对于分页功能,传统的实现方法是在后端完成分页然后分批返回到前端的,比如可能会返回一段这样的数据: { total:500,page:1,data:[] } 也就是后端会返回分好页的数据,含有表示总数据量的 然而,我这个页面只是个静态页面,数据是放在Github Issues上的通过API获取的。(Github Issues的分页貌似不能自定义数量...),所以没法直接返回分好的数据,所以只能在前端强行分页~ 分页功能这一块我偷懒了...用的是 官方文档:https://ant.design/components/pagination-cn/ 文档很清晰,使用起来也特别简单。 前端渲染的逻辑(有点蠢):将数据存放到一个数组中,根据当前页数和每页显示条数来计算该显示的索引值,取出相应的数据即可。 翻页组件中: constructor() { super(); this.onChangePage = this.onChangePage.bind(this); } onChangePage(pageNumber) { this.props.handlePageChange(pageNumber); } render() { return ( <div className="blog-article-paging"> <Pagination onChange={this.onChangePage} defaultPageSize={this.props.defaultPageSize} total={this.props.total} /> </div> ); } 当页数发生改变后,会触发从父组件传进 父组件中: handlePageChange(pageNumber) { this.setState({ currentPage: pageNumber }); } render() { return ( <div className="archive-list-area"> <ArticleList issues={this.props.issues} defaultPageSize={this.state.defaultPageSize} pageNumber={this.state.currentPage} /> <ArticlePaging handlePageChange={this.handlePageChange} total={this.props.issues.length} defaultPageSize={this.state.defaultPageSize} /> </div> ); } 列表中: render() { const articlelist = []; const issues = this.props.issues; const currentPage = this.props.pageNumber; const defaultPageSize = this.props.defaultPageSize; const start = currentPage === 1 ? 0 : (currentPage - 1) * defaultPageSize; const end = start + defaultPageSize < issues.length ? start + defaultPageSize : issues.length; for (let i = start; i < end; i += 1) { const item = issues[i]; articlelist.push(<ArticleItem />); } } label在 这里说明一下,
即使有新的 类别
在这里的思路主要就是:遍历所有 const categoryList = []; const categoryHash = {}; for (let i = 0; i < issues.length; i += 1) { const labels = issues[i].labels; for (let j = 0; j < labels.length; j += 1) { if (labels[j].color === COLOR_LABEL_CATEGORY) { const category = labels[j].name; if (categoryHash[category] === undefined) { categoryHash[category] = true; const categoryTemp = { category,sum: 1 }; categoryList.push(categoryTemp); } else { for (let k = 0; k < categoryList.length; k += 1) { if (categoryList[k].category === category) { categoryList[k].sum += 1; } } } } } } 这样实现得要经历三次循环,复杂度有点高,感觉有点蠢,有待改进,如果有更好的方法,请多多指教~ 标签
这里的思路和类别的思路基本一样,只不过不同的显示方式而已。 本来这里是想通过字体大小来体现每个标签的权重,后来觉得可能对于我来说,暂时只有那几个标签会很频繁,其它标签可能会很少,用字体大小来区分就没有什么意义,还是改成排序的方式。 文章页
文章页主要分为两部分:
文章内容有两种方式获取文章具体内容:
最后我选择了后者。 文章是用 除了渲染 章节目录首先,这里有一个 文章内容是通过 由于我不太熟悉正则表达式,曾经还在sf上咨询过,就采用了其中一个答案: const issues = content; const menu = []; const patt = /(#+)s+?(.+)/g; let result = null; while ((result = patt.exec(issues))) { menu.push({ level: result[1].length,title: result[2] }); } 这样可以获取到所有的 这里还有一个问题,本来通过 归档页按年份归档:
按类别归档:
按标签归档:
问题基本功能是已经基本实现了,现在还存在着以下几个问题,也算是一个
原文地址:https://github.com/axuebin/react-blog/issues/17 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |