react同构项目的总结

1,015 阅读5分钟

总结

  花了一周事件完成了全栈进阶课程 React16.8+Next.js+Koa2一步到位开发Github这个项目,总的来说完成这个项目不容易。先后查阅了next和koa的资料,学习了hooksAPI的知识;总的来说,对个人提升还是很大的。

nextjs

  nextjs是react服务端渲染的同构框架,主要针对react无法被搜索引擎爬取的问题。

什么是SSR

  服务端渲染主要是在服务端将数据等等渲染成html,然后返回给客户端这个html字符串。剩下的一切都由客户端渲染完成。注意:每次第一次加载的页面是由服务端渲染完成的,客户端加载html字符串;剩下的均是由客户端请求完成

SSR的难点

  服务端渲染的难点在于同步客户端和服务端的数据。
  在nextjs中他为pages下的每个页面组件都定义了静态方法getInitialProps方法,这个方法是用来在服务端渲染的时候发送请求得到数据,这个方法返回一个对象,这个对象即页面组件的初始数据,这些数据会被添加到页面组件的props上。

SSR优化性能的方法

  在SSR中,我们需要在每个页面组件的getInitialProps方法上返回初次加载页面所需要的数据,在每次请求的时候我们可以对数据进行缓存,可以使用lru-cache包进行相关数据的缓存操作。
  除此之外,react提供的useEffect、useCallback、memo、useMemo这些hook也为性能优化提供了很好的解决办法。

SSR数据同步

  在react同构项目中仅靠next是无法做到数据同步的,所以一般来说会搭配一个node框架进行相关的集成操作。

Github OAuth

  Github OAuth是OAuth2.0协议的一种第三方接入方式,主要用于用户的认证和授权。

接口代理

  值得一提的是由于Github OAuth的这一特性,我们就需要进行接口代理。接口代理即客户端请求本地服务端,然后本地服务端向github接口请求数据,数据返回之后将数据返回给客户端。
  在使用接口代理的情况下,我们可以很安全的在服务端进行token的认证和一些头部的设置。
  注意事项:启用接口代理的情况下,由于是服务端渲染我们需要在代理接口分清楚是服务端还是客户端渲染,判断是否是客户端渲染的条件即判断window是否存在,通过这个标志,我们在服务端请求githubAPI时加上响应的头部和请求的地址,若是客户端请求则直接请求github接口。

koa

  koa框架是轻量级的基于node的服务器框架,和express相比,它的效率更高,更轻量。

将next当成中间件

  由于next数据同步的问题,我们使用koa进行数据同步时的服务端。集成之后最重要的一个参数就是koa的ctx上下文对象。这个ctx上下文对象包含了reqres等等一系列的对象。
  在集成的时候最重要的代码如下:

 router.get('*', async ctx => {
    ctx.req.session = ctx.session;
    await handle(ctx.req, ctx.res);
    ctx.respond = false
  });
  // 使用中间件
  server.use(async (ctx, next) => {
    ctx.res.statusCode = 200;
    await next()
  });

代码复用

  代码复用也是这个课程最大的特色之一,通过高阶组件的编写我们很容易的可以进行组件的复用。除此之外,对多个页面共同存在的组件进行抽离成单独的组件,然后向下传递下一个组件所需要的数据。

markdown文件的处理

markdown解码

  在github的readme中markdown文件采用的是base64编码方式,在客户端我们可以使用window.atob方法对其进行解码,但是在服务端却不行。服务端渲染时,我们需要在使用的markdown组件导入atob包。由于存在中文字符,我们还需要将解码后的字符串转为UTF8编码的字符串。

const base64ToUTF8 = str => {
  return decodeURIComponent(escape(atob(str)))
};

markdown渲染

  在将markdown渲染成html时我们需要安装markdown-it包,除此之外还需要导入github-markdown-css包,对html进行样式的设置。
markdownRender.js

import 'github-markdown-css'
import MarkDownIt from 'markdown-it'
import {memo, useMemo} from 'react'

const md = new MarkDownIt({
  // 将md转换成html
  html: true,
  // 将md中的链接转化成a标签
  linkify: true
});
const base64ToUTF8 = str => {
  return decodeURIComponent(escape(atob(str)))
};
export default memo(({content, isBase64}) => {
  const markdown = isBase64 ? base64ToUTF8(content) : content;
  // 只要markdown不发生改变,那么html就不发生改变,那么该组件就不会发生变化
  const html = useMemo(() => md.render(markdown), [markdown]);
  return (
      <div className={'markdown-body'}>
        <div dangerouslySetInnerHTML={{__html: html}}/>
      </div>
  )
})

container组件的小技巧

有的时候我们需要将组件进行同意的设置又不想在渲染的组件外多包一层dom的时候就需要进行这方面的代码技巧。
container.js

/**
 * 运用cloneElement完成组件的扩展。cloneElement拷贝得到的组件和createElement的参数一样。
 */

import {cloneElement} from "react";

const style = {
  width: '100%',
  maxWidth: 1200,
  marginLeft: 'auto',
  marginRight: 'auto',
  paddingLeft: 20,
  paddingRight: 20
};
export default ({children, renderer = <div/>}) => {
  return cloneElement(renderer, {
    {/* 对style进行合并 */}
    style: Object.assign({}, style, renderer.props.style),
    children
  })
}

useContainer.js

 <Container renderer={<div style={{fontSize: 20}}></div>}>
        {children}
 </Container>

项目源码在这里