React 服务端渲染框架 Next.js 基于 Gank api 实战
最开始先摆出地址,有在线 demo,目前只支持 pc:https://github.com/OrangeXC/gank 鉴于最近 vue 相关的文章写的比较多,抽出时间写点 react 的项目,当时用 react 还是 v15 现在都 v16 了,感慨跟不上所有框架的节奏(玩笑话),框架的本质都是大同小异的,每次高 star 框架更新看一下 change 是个好习惯。 之前用 Nuxt 写了个简单的 v2ex,今天的主角依然是 SSR 服务端渲染 Nuxt 文档里有写到灵感源于 Next.js,那么就是说 Next.js 算是 SSR 框架中的元老级别的了。 为什么选择 SSR 框架前面的文章总是在官方文档上小费功夫说明下,这里对于不熟悉 Next.js 的读者建议直接转到 Github,别犹豫,当然熟悉 Nuxt 也可以无障碍阅读本文 不论是 Next.js 或 Nuxt,服务端渲染框架主要两个重要功能
至于它们是基于哪个前端库封装的,还要看库本身是否支持 SSR,然后就是对外提供 render 函数。 用此类库的原因也不必多说,节省开发成本,不再纠结于环境搭建以及渲染细节。 直接开工本次要实现的是基于 gank api 的项目,还是看人家支持什么 api,点前面链接查看详细 api 大体总结为 => 列表,搜索,提叫到审核 列表分为了许多类型,主要的 menu 也是针对不同类型的列表展开 路由通过已知的 api 可以轻松的定义路由
同 Nuxt 路由配置文件不需要手动创建,/pages 下默认会渲染为页面,文件名自然就是路由名 路由文件都创建完了,下一步思考如抽离出公共模板 Layout 代码,Next.js 提供了 layout-component example 我们可以在里面定义 Head,Header,Footer,当然要留出一个内容区域的插槽 引用于 example 的 layout.js 代码 import Link from 'next/link' import Head from 'next/head' export default ({ children,title = 'This is the default title' }) => ( <div> <Head> <title>{ title }</title> <meta charSet='utf-8' /> <meta name='viewport' content='initial-scale=1.0,width=device-width' /> </Head> <header> <nav> <Link href='/'><a>Home</a></Link> | <Link href='/about'><a>About</a></Link> | <Link href='/contact'><a>Contact</a></Link> </nav> </header> { children } <footer> {'I`m here to stay'} </footer> </div> ) 因为本次使用的是 antd 做 ui,固实现动态的导航展示上要注意些小问题,我们需要根据 path 动态的给 menu 激活状态。 两个解决方案: 1.在 pages 里面的每一个路由页面里获取 pathname,初始化方法
调用方法也简单 static async getInitialProps({ pathname }) { return { pathname } } 这样一来可以通过传参到 layout 组件的方式 在 Layout 里面改变 Meun 的 active 2.写一个 ActiveLink 组件,再封装一层原有的 Menu 在选择方案前还是要看官方有没有 example,于是找到了 using-with-router 引用于 example 的 ActiveLink.js 代码 import { withRouter } from 'next/router' // typically you want to use `next/link` for this usecase // but this example shows how you can also access the router // using the withRouter utility. const ActiveLink = ({ children,router,href }) => { const style = { marginRight: 10,color: router.pathname === href ? 'red' : 'black' } const handleClick = (e) => { e.preventDefault() router.push(href) } return ( <a href={href} onClick={handleClick} style={style}> {children} </a> ) } export default withRouter(ActiveLink) 简单易懂,在 withRouter 方法里可以取到 router 实例,这样可以取到 pathname,query 等等。 这里只需要稍稍修改下 style,变成 antd 的 className,如下 const ActiveLink = ({ children,href }) => { const active = router.pathname === href const className = active ? 'ant-menu-item-selected ant-menu-item' : 'ant-menu-item' return ( <li href='#' onClick={onClickHandler(href)} className={className} role="menuitem" aria-selected="false"> {children} </li> ) } 在 Layout 组件的 Menu 里直接使用 ActiveLink 组件即可,到这为止解决了全部路由相关问题和 Layout 组件问题 数据流解决了路由问题下一步就是每个页面的 content 的数据填充 我们依旧是在 这里涉及到一个 node 和 Browserify 同构的 fetch 库 isomorphic-fetch,cli 工具应该会自带这个库,没有的话提前安装下。 到这里就不用担心 fetch api 在服务端的问题了,这里获取的列表数据走的接口基本一致 三个变量 type-类型、perPage-每页数量、page-页数 接下来可以把 List 和 ListItem 抽象出来,成为共用的组件,每个页面都可以调用,这里不详细展开说明,简单的使用 antd 的 Card 组件,没有特殊功能。 每个页面的请求数据部分也基本一致,将数据存到 props 里,传入 List 组件中去 形成了简单的单向数据流动 列表页面 page组件(fetch data) -> List组件(继承自 Layout) -> ListItem组件 时间轴页面 page组件(fetch data) -> Timeline组件(继承自 Layout) 提交干货页面 page组件 -> Form组件(继承自 Layout) -> post请求(发送formData) 搜索页面 page组件 -> Input组件+空ListItem组件(继承自 Layout) -> get请求(获取关键词对应query的列表数据) -> ListItem组件 Mobx既然前面说清楚了数据流都十分简单,那么为什么要引入全局状态管理徒增烦恼呢? 有一点无奈的地方是 这里我们要分页功能,但是首屏数据是 props 的,我们换页之后没办法更新 props 的值,也就是没办法再次执行 最简单粗暴的方式就是放弃 spa 的动态切换数据,我们每次 能不能解决问题,答案是能解决问题,那么既然是分页组件,人家 antd 也提供了 Pagination 组件,问题一个接着一个,人家返回的列表并没有告诉你 totalCount,没有 totalCount 就没办法知道有多少页。。。 好尴尬的问题,这个分页没法做,怒脸~~~ 也不是没办法做,这个问题变向思考下可以做 loadMore,没错加载更多,当加载到最后一页(即的列表长度小于 perPage)或是此页恰巧等于 perPage 但下一页为空数组时,我们给一个提示,没有更多内容了。 涉及到向 props 的 list 里 还是去找 example,with mobx 引用于 example 的 store.js 代码 export function initStore (isServer,lastUpdate = Date.now()) { if (isServer) { return new Store(isServer,lastUpdate) } else { if (store === null) { store = new Store(isServer,lastUpdate) } return store } } 这段代码太简单,没必要解释了,总之我们在初始化页面时调用 initStore 就好了,isServer 通过 然后在 loadMore 时出发一个 action @action loadMoreList = (more) => { this.list = this.list.concat(more) } 到这加载更多的功能也就实现了,不足的一点是 List 组件里的 handleScroll () { if (document.documentElement.offsetHeight + document.documentElement.scrollTop > document.documentElement.scrollHeight - 50) { this.handleLoadMore() } } 其它代码感兴趣可以直接取仓库看,没有阅读难度。 表单提交说到其它页的 fetch list 没什么可将全都是 get 请求,fetch 发一个 get 请求十分简单,不用声明请求类型。 fetch 操作 post 也仅仅在于设置 method 为 POST 之所以单独一章说表单提交,因为在提交表单时遇到了一些问题,由于要 fetch 模拟 form 的 post 请求 看了这个 issue:https://github.com/matthew-an... 开始怀疑人生,试了所有方法 POST,也走的通,但是接口返回的 msg 就是没接收到参数。 想了想还是回归到笨方法一个一个将参数拼接进去,没想到较优雅的方式,给出代码,同时欢迎讨论 handleSubmit = (e) => { e.preventDefault() this.props.form.validateFieldsAndScroll(async (err,values) => { if (!err) { this.setState({ submitLoading: true }) let strList = [] Object.keys(values).forEach(item => { strList.push(`${item}=${values[item]}`) }) const res = await fetch("https://gank.io/api/add2gank",{ method: "POST",headers: { 'Content-Type': 'application/x-www-form-urlencoded' },body: strList.join('&') }) const json = await res.json() if (json.error) { message.error(json.msg) } else { message.success(json.msg) } this.setState({ submitLoading: false }) } }) }
微交互既然功能差不多了,再微交互上再加把劲,用过 NUXT 的知道 NUXT 内置了 Loading bar,切换路由时在页面顶端会有 loading 条,体验较好。 next.js 并没有内置这个功能,页面看起来会显得十分怪异,点击切换路由没有反应,顿一下再跳转,顿的时候在获取初始化数据。 官方推荐使用 nprogress 关键代码如下,写在了 Layout.js 组件里 Router.onRouteChangeStart = (url) => { console.log(`Loading: ${url}`) NProgress.start() } Router.onRouteChangeComplete = () => NProgress.done() Router.onRouteChangeError = () => NProgress.done() 这样整个网站看起来洋气多了,切换 router 页面顶端有 loading bar,右上角还有 loading icon 上线开发 next.js 的组织叫 zeit,在官网他们的得意作品是 now,一个快速部署的工具,同时为免费用户提供三个免费的服务,支持 docker,node 等 看 5 分钟文档就能上手部署 node 项目,比 Heroku 简单的多 这里使用的就是 now,首先安装 now-cli 在项目根路径下一句命令部署 now 线上的路径就不贴出来了,时刻关注 Github 上方的 website 地址,因为每次部署不绑定域名的情况下是 至于上线就讲这么多,有疑问欢迎交流。 未来下一步要解决几个问题
说到福利页面本想着不加来着,因为个别写 demo 的人专门把福利列表拎出来做成妹子 App,既然是干货集中营,就应该多些技术元素,福利都是次要的。 总结到这为止一个 next.js 版本的 gank(干货集中营)完成了,感慨现在开发工具越来越好用,还是之前的想法把好用的工具分享给大家,给一个完整的例子供学习者参考,不再每次都看各个版本的 Hacker News,而是给国内的学习者一个中文版的例子,同时文中也会将实现的时候遇到的问题。 本人 orange 也是再不断的学习当中,本文也是第一次接触学习 next.js 写的项目,文章或项目有不足之处欢迎指正,感谢阅读!
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |