详解React+Koa实现服务端渲染(SSR)

React是目前前端社区最流行的UI库之一,它的基于组件化的开发方式极大地提升了前端开发体验,React通过拆分一个大的应用至一个个小的组件,来使得我们的代码更加的可被重用,以及获得更好的可维护性,等等还有其他很多的优点...

通过React, 我们通常会开发一个单页应用(SPA),单页应用在浏览器端会比传统的网页有更好的用户体验,浏览器一般会拿到一个body为空的html,然后加载script指定的js, 当所有js加载完毕后,开始执行js, 最后再渲染到dom中, 在这个过程中,一般用户只能等待,什么都做不了,如果用户在一个高速的网络中,高配置的设备中,以上先要加载所有的js然后再执行的过程可能不是什么大问题,但是有很多情况是我们的网速一般,设备也可能不是最好的,在这种情况下的单页应用可能对用户来说是个很差的用户体验,用户可能还没体验到浏览器端SPA的好处时,就已经离开网站了,这样的话你的网站做的再好也不会有太多的浏览量。

但是我们总不能回到以前的一个页面一个页面的传统开发吧,现代化的UI库都提供了服务端渲染(SSR)的功能,使得我们开发的SPA应用也能完美的运行在服务端,大大加快了首屏渲染的时间,这样的话用户既能更快的看到网页的内容,与此同时,浏览器同时加载需要的js,加载完后把所有的dom事件,及各种交互添加到页面中,最后还是以一个SPA的形式运行,这样的话我们既提升了首屏渲染的时间,又能获得SPA的客户端用户体验,对于SEO也是个必须的功能。

OK,我们大致了解了SSR的必要性,下面我们就可以在一个React App中来实现服务端渲染的功能,BTW, 既然我们已经处在一个到处是async/await的环境中,这里的服务端我们使用koa2来实现我们的服务端渲染。

初始化一个普通的单页应用SPA

首先我们先不管服务端渲染的东西,我们先创建一个基于React和React-Router的SPA,等我们把一个完整的SPA创建好后,再加入SSR的功能来最大化提升app的性能。

首先进入app入口 App.js:

import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const Home = () => <div>Home</div>;
const Hello = () => <div>Hello</div>;

const App = () => {
 return (
  <Router>
   <Route exact path="/" component={Home} />
   <Route exact path="/hello" component={Hello} />
  </Router>
 )
}

ReactDOM.render(<App/>, document.getElementById('app'))

上面我们为路由/ 和 /hello创建了2个只是渲染一些文字到页面的组件。但当我们的项目变得越来越大,组件越来越多,最终我们打包出来的js可能会变得很大,甚至变得不可控,所以呢我们第一步需要优化的是代码拆分(code-splitting),幸运的是通过webpack dynamic importreact-loadable,我们可以很容易做到这一点。

用React-Loadable来时间代码拆分

使用之前,先安装 react-loadable:

npm install react-loadable
# or
yarn add react-loadable

然后在你的 javascript中:

//...
import Loadable from 'react-loadable';
//...

const AsyncHello = Loadable({
 loading: <div>loading...</div>,
 //把你的Hello组件写到单独的文件中
 //然后使用webpack的 dynamic import
 loader: () => import('./Hello'),
})

//然后在你的路由中使用loadable包装过的组件:
<Route exact path="/hello" component={AsyncHello} />

很简单吧,我们只需要import react-loadable, 然后传一些option进去就行了,其中的loading选项是当动态加载Hello组件所需的js时,渲染loading组件,给用户一种加载中的感觉,体验也会比什么都不加好。

好了,现在如果我们访问首页的话,只有Home组件依赖的js才会被加载,然后点击某个链接进入hello页面的话,会先渲染loading组件,并同时异步加载hello组件依赖的js,加载完后,替换掉loading来渲染hello组件。通过基于路由拆分代码到不同的代码块,我们的SPA已经有了很大的优化,cheers🍻。更叼的是react-loadable同样支持SSR,所以你可以在任意地方使用react-loadable,不管是运行在前端还是服务端,要让react-loadable在服务端正常运行的话我们需要做一些额外的配置,本文后面会讲到,先不急🏃。‍

到这里我们已经创建好一个基本的React SPA,加上代码拆分,我们的app已经有了不错的性能,但是我们还可以更加极致的优化app的性能,下面我们通过增加SSR的功能来进一步提升加载速度,顺便解决一下SPA中的SEO问题🎉。

加入服务端渲染(SSR)功能

首先我们先搭建一个最简单的koa web服务器:

npm install koa koa-router

然后在koa的入口文件app.js中:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();
router.get('*', async (ctx) => {
 ctx.body = `
   <!DOCTYPE html>
    <html lang="en">
    <head>
     <meta charset="UTF-8">
     <title>React SSR</title>
    </head>
    <body>
     <div id="app"></div>
     <script type="text/javascript" src="/bundle.js"></script>
    </body>
   </html>
  `;
});

app.use(router.routes());
app.listen(3000, '0.0.0.0');

上面*路由代表任意的url进来我们都默认渲染这个html,包括html中打包出来的js,你也可以用一些服务端模板引擎(如:nunjucks)来直接渲染html文件,在webpack打包时通过html-webpack-plugin来自动插入打包出来的js/css资源路径。

OK, 我们的简易koa server好了,接下来我们开始编写React SSR的入口文件AppSSR.js,这里我们需要使用StaticRouter来代替之前的BrowserRouter,因为在服务端,路由是静态的,用BrowserRouter的话是不起作用的,后面还会做一些配置来使得react-loadable运行在服务端。

提示: 你可以把整个node端的代码用ES6/JSX风格编写,而不是部分commonjs,部分JSX, 但这样的话你需要用webpack把整个服务端的代码编译成commonjs风格,才能使得它运行在node环境中,这里的话我们把React SSR的代码单独抽出去,然后在普通的node代码里去require它。因为可能在一个现有的项目中,之前都是commonjs的风格,把以前的node代码一次性转成ES6的话成本有点大,但是可以后期一步步的再迁移过去

OK, 现在在你的 AppSRR.js中:

import React from 'react';
//使用静态 static router
import { StaticRouter } from 'react-router-dom';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
//下面这个是需要让react-loadable在服务端可运行需要的,下面会讲到
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';

//这里吧react-router的路由设置抽出去,使得在浏览器跟服务端可以共用
//下面也会讲到...
import AppRoutes from 'src/AppRoutes';

//这里我们创建一个简单的class,暴露一些方法出去,然后在koa路由里去调用来实现服务端渲染
class SSR {
 //koa 路由里会调用这个方法
 render(url, data) {
  let modules = [];
  const context = {};
  const html = ReactDOMServer.renderToString(
   <Loadable.Capture report={moduleName => modules.push(moduleName)}>
    <StaticRouter location={url} context={context}>
     <AppRoutes initialData={data} />
    </StaticRouter>
   </Loadable.Capture>
  );
  //获取服务端已经渲染好的组件数组
  let bundles = getBundles(stats, modules);
  return {
   html,
   scripts: this.generateBundleScripts(bundles),
  };
 }
 //把SSR过的组件都转成script标签扔到html里
 generateBundleScripts(bundles) {
  return bundles.filter(bundle => bundle.file.endsWith('.js')).map(bundle => {
   return `<script type="text/javascript" src="${bundle.file}"></script>\n`;
  });
 }

 static preloadAll() {
  return Loadable.preloadAll();
 }
}

export default SSR;

当编译这个文件的时候,在webpack配置里使用target: "node" externals,并且在你的打包前端app的webpack配置中,需要加入react-loadable的插件,app的打包需要在ssr打包之前运行,不然拿不到react-loadable需要的各组件信息,先来看app的打包:

//webpack.config.dev.js, app bundle
const ReactLoadablePlugin = require('react-loadable/webpack')
 .ReactLoadablePlugin;

module.exports = {
 //...
 plugins: [
  //...
  new ReactLoadablePlugin({ filename: './build/react-loadable.json', }),
 ]
}

在.babelrc中加入loadable plugin:

{
 "plugins": [
   "syntax-dynamic-import",
   "react-loadable/babel",
   ["import-inspector", {
    "serverSideRequirePath": true
   }]
  ]
}

上面的配置会让react-loadable知道哪些组件最终在服务端被渲染了,然后直接插入到html script标签中,并在前端初始化时把SSR过的组件考虑在内,避免重复加载,下面是SSR的打包:

//webpack.ssr.js
const nodeExternals = require('webpack-node-externals');

module.exports = {
 //...
 target: 'node',
 output: {
  path: 'build/node',
  filename: 'ssr.js',
  libraryExport: 'default',
  libraryTarget: 'commonjs2',
 },
 //避免把node_modules里的库都打包进去,此ssr js会直接运行在node端,
 //所以不需要打包进最终的文件中,运行时会自动从node_modules里加载
 externals: [nodeExternals()],
 //...
}

然后在koa app.js, require它,并且调用SSR的方法:

//...koa app.js
//build出来的ssr.js
const SSR = require('./build/node/ssr');
//preload all components on server side, 服务端没有动态加载各个组件,提前先加载好
SSR.preloadAll();

//实例化一个SSR对象
const s = new SSR();

router.get('*', async (ctx) => {
 //根据路由,渲染不同的页面组件
 const rendered = s.render(ctx.url);

 const html = `
  <!DOCTYPE html>
   <html lang="en">
   <head>
    <meta charset="UTF-8">
   </head>
   <body>
    <div id="app">${rendered.html}</div>
    <script type="text/javascript" src="/runtime.js"></script>
    ${rendered.scripts.join()}
    <script type="text/javascript" src="/app.js"></script>
   </body>
  </html>
 `;
 ctx.body = html;
});
//...

以上是个简单的实现React SSR到koa web server, 为了使react-loadable知道哪些组件在服务端渲染了,rendered里面的scripts数组里面包含了SSR过的组件组成的各个script标签,里面调用了SSR#generateBundleScripts()方法,在插入时需要确保这些script标签在runtime.js之后((通过 CommonsChunkPlugin 来抽出来)),并且在app bundle之前(也就是初始化的时候应该已经知道之前的哪些组件已经渲染过了)。更多react-loadable服务端支持,参考这里.

上面我们还把react-router的路由都单独抽出去了,使得它可以运行在浏览器跟服务端,以下是AppRoutes组件:

//AppRoutes.js
import Loadable from 'react-loadable';
//...

const AsyncHello = Loadable({
 loading: <div>loading...</div>,
 loader: () => import('./Hello'),
})

function AppRoutes(props) {
 <Switch>
  <Route exact path="/hello" component={AsyncHello} />
  <Route path="/" component={Home} />
 </Switch>
}

export default AppRoutes

//然后在 App.js 入口中
import AppRoutes from './AppRoutes';
// ...
export default () => {
 return (
  <Router>
   <AppRoutes/>
  </Router>
 )
}

服务端渲染的初始状态

目前为止,我们已经创建了一个React SPA,并且能在浏览器端跟服务端共同运行🍺,社区称之为universal app 或者 isomophic app。但是我们现在的app还有一个遗留问题,一般来说我们app的数据或者状态都需要通过远端的api来异步获取,拿到数据后我们才能开始渲染组件,服务端SSR也是一样,我们要动态的获取初始数据,然后才能扔给React去做SSR,然后在浏览器端我们还要初始化就能同步获取这些SSR时的初始化数据,避免浏览器端初始化时又重新获取了一遍。

下面我们简单从github获取一些项目的信息作为页面初始化的数据, 在koa的app.js中:

//...
const fetch = require('isomorphic-fetch');

router.get('*', async (ctx) => {
 //fetch branch info from github
 const api = 'https://api.github.com/repos/jasonboy/wechat-jssdk/branches';
 const data = await fetch(api).then(res => res.json());

 //传入初始化数据
 const rendered = s.render(ctx.url, data);

 const html = `
  <!DOCTYPE html>
   <html lang="en">
   <head>
    <meta charset="UTF-8">
   </head>
   <body>
    <div id="app">${rendered.html}</div>

    <script type="text/javascript">window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>

    <script type="text/javascript" src="/runtime.js"></script>
    ${rendered.scripts.join()}
    <script type="text/javascript" src="/app.js"></script>
   </body>
  </html>
 `;
 ctx.body = html;
});

然后在你的Hello组件中,你需要checkwindow里面(或者在App入口中统一判断,然后通过props传到子组件中)是否存在window.__INITIAL_DATA__,有的话直接用来当做初始数据,没有的话我们在componentDidMount生命周期函数中再去来数据:

export default class Hello extends React.Component {
 constructor(props) {
  super(props);

  this.state = {
   //这里直接判断window,如果是父组件传入的话,通过props判断
   github: window.__INITIAL_DATA__ || [],
  };
 }

 componentDidMount() {
  //判断没有数据的话,再去请求数据
  //请求数据的方法也可以抽出去,以让浏览器及服务端能统一调用,避免重复写
  if (this.state.github.length <= 0) {
   fetch('https://api.github.com/repos/jasonboy/wechat-jssdk/branches')
    .then(res => res.json())
    .then(data => {
     this.setState({ github: data });
    });
  }
 }

 render() {
  return (
   <div>
    <ul>
     {this.state.github.map(b => {
      return <li key={b.name}>{b.name}</li>;
     })}
    </ul>
   </div>
  );
 }
}

好了,现在如果页面被服务端渲染过的话,浏览器会拿到所有渲染过的html, 包括初始化数据,然后通过这些SSR的内容配合加载的js,再组成一个完整的SPA,就像一个普通的SPA一样,但是我们得到了更好的性能,更好的SEO😎。

React-v16 更新

在React的最新版v16中,SSR的API做了很多的优化,并且提供了新的基于流的API来更好的提升性能,通过streaming api, 服务端可以边渲染边把前面渲染好的html发到浏览器,浏览器端也可以提前开始渲染页面而不是等服务端所有组件都渲染完成后才能开始浏览器端的初始化,提升了性能也降低了服务端资源的消耗。还有一个在浏览器端需要注意的是需要使用ReactDOM.hydrate()来代替之前的ReactDOM.render(),更多的更新参考medium文章whats-new-with-server-side-rendering-in-react-16.

💖要查看完整的demo, 参考koa-web-kit, koa-web-kit是一个现代化的基于React/Koa的全栈开发框架,包括React SSR支持,可以直接用来测试服务端渲染的功能😀

结论

好了,以上就是React-SSR + Koa的简单实践,通过SSR,我们既提升了性能,又很好的满足了SEO的要求,Best of the Both Worlds🍺。

English Version

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解React 在服务端渲染的实现

    React是最受欢迎的客户端 JavaScript 框架,但你知道吗(可以试试),你可以使用 React 在服务器端进行渲染? 假设你已经在客户端使用 React 构建了一个事件列表 app.该应用程序使用了您最喜欢的服务器端工具构建的API.几周后,用户告诉您,他们的页面没有显示在 Google 上,发布到 Facebook 时也显示不出来. 这些问题似乎是可以解决的,对吧? 您会发现,要解决这个问题,需要在初始加载时从服务器渲染 React 页面,以便来自搜索引擎和社交媒体网站的爬虫工具可以

  • 详解React项目的服务端渲染改造(koa2+webpack3.11)

    因为对网页SEO的需要,要把之前的React项目改造为服务端渲染,经过一番调查和研究,查阅了大量互联网资料.成功踩坑. 选型思路:实现服务端渲染,想用React最新的版本,并且不对现有的写法做大的改动,如果一开始就打算服务端渲染,建议直接用NEXT框架来写 项目地址:https://github.com/wlx200510/react_koa_ssr 脚手架选型:webpack3.11.0 + react Router4 + Redux + koa2 + React16 + Node8.x 主要

  • 详解React 服务端渲染方案完美的解决方案

    最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法.在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢? 什么是服务器端渲染 使用 React 构建客户端应用程序,默认情况下,可以在浏览器中输出 React 组件,进行生成 DOM 和操作 DOM.React 也可以在服务端通过 Node.js 转换成 HTML,直接在浏览器端"呈现"处理好的 HTML 字符串,这个过程可以被认为 "同构&qu

  • React服务端渲染(总结)

    一.前言 为什么需要服务端渲染?什么情况下进行服务端渲染?笔者认为,当我们要求渲染时间尽量快.页面响应速度快时(优点),才会采用服务器渲染,并且应该"按需"对页面进行渲染 --"首次加载/首屏".即服务端渲染的优势在于:由中间层( node端 )为客户端请求初始数据.并由node渲染页面.那客户端渲染和服务端渲染有什么差别?服务端渲染究竟快在哪里呢? 二.原因与思路 客户端渲染路线:1. 请求一个html -> 2. 服务端返回一个html -> 3.

  • 利用React Router4实现的服务端直出渲染(SSR)

    我们已经熟悉React 服务端渲染(SSR)的基本步骤,现在让我们更进一步利用 React RouterV4 实现客户端和服务端的同构.毕竟大多数的应用都需要用到web前端路由器,所以要让SSR能够正常的运行,了解路由器的设置是十分有必要的 基本步骤 路由器配置 前言已经简单的介绍了React SSR,首先我们需要添加ReactRouter4到我们的项目中 $ yarn add react-router-dom # or, using npm $ npm install react-router

  • 详解React服务端渲染从入门到精通

    前言 这篇文章是我自己在搭建个人网站的过程中,用到了服务端渲染,看了一些教程,踩了一些坑.想把这个过程分享出来. 我会尽力把每个步骤讲明白,将我理解的全部讲出来. 文中的示例代码来自于这个仓库,也是我正在搭建的个人网站,大家可以一起交流一下.示例代码因为简化,与仓库代码有些许出入 本文中用到的技术 React V16 | React-Router v4 | Redux | Redux-thunk | express React 服务端渲染 服务端渲染的基本套路就是用户请求过来的时候,在服务端生成

  • 详解react服务端渲染(同构)的方法

    学习react也有一段时间了,使用react后首页渲染的速度与seo一直不理想.打算研究一下react神奇服务端渲染. react服务端渲染只能使用nodejs做服务端语言实现前后端同构,在后台对react组件进行解析并生成html字符串后返回视图页面. 后台为什么可以解析react组件?因为Node.js是一个Javascript运行环境,nodejs与javascript语法基本是相同的,所以nodejs可以正常解析react组件. 一.准备动作 1.安装nodejs与安装express 安

  • 使用Node搭建reactSSR服务端渲染架构

    如题:本文所讲架构主要用到技术栈有: Node, Express, React, Mobx, webpack4, ES6, ES7, axios, ejs,  log4js, scss,echarts,ant desige SSR的概念 Server Slide Rendering,缩写为 ssr,即服务器端渲染,因为是后端出身,所以其实早就明白是怎么回事,只是没这个具体名词的概念罢了,这个词被频繁提起也是拜近年来前端飞速发展所赐,主要针对 SPA应用,目的大概有以下几个: 解决单页面应用的 S

  • 详解React+Koa实现服务端渲染(SSR)

    React是目前前端社区最流行的UI库之一,它的基于组件化的开发方式极大地提升了前端开发体验,React通过拆分一个大的应用至一个个小的组件,来使得我们的代码更加的可被重用,以及获得更好的可维护性,等等还有其他很多的优点... 通过React, 我们通常会开发一个单页应用(SPA),单页应用在浏览器端会比传统的网页有更好的用户体验,浏览器一般会拿到一个body为空的html,然后加载script指定的js, 当所有js加载完毕后,开始执行js, 最后再渲染到dom中, 在这个过程中,一般用户只能

  • 详解Nuxt.js Vue服务端渲染摸索

    本文采用nuxt进行服务端渲染https://zh.nuxtjs.org/ Nuxt.js 十分简单易用.一个简单的项目只需将 nuxt 添加为依赖组件即可. Vue因其简单易懂的API.高效的数据绑定和灵活的组件系统,受到很多前端开发人员的青睐.国内很多公司都在使用vue进行项目开发,我们正在使用的简书,便是基于Vue来构建的. 我们知道,SPA前端渲染存在两大痛点:(1)SEO.搜索引擎爬虫难以抓取客户端渲染的页面meta信息和其他SEO相关信息,使网站无法在搜索引擎中被用户搜索到.(2)用

  • 详解基于vue的服务端渲染框架NUXT

    Nuxt 随着现在vue和react的流行,许多网站都做成了SPA,确实提升了用户体验,但SPA也有两个弱点,就是SEO和首屏渲染速度.为了解决单页应用的痛点,基于vue和react的服务端渲染应运而生.由于公司的框架采用的是vue,所以就简单的研究了一下基于vue的服务端渲染框架--NUXT.在vue的官网有关于服务端渲染的详细介绍,而NUXT集成了利用Vue开发服务端渲染的应用所需要的各种配置,也集成了Vue2.vue-router.vuex.vux-meta(管理页面meta信息的),利用

  • Vue 服务端渲染SSR示例详解

    目录 手写Vue服务端渲染 一.开始vue-ssr之旅 二.采用模板渲染 三.ssr目录创建 四.通过webpack实现编译vue项目 app.js client-entry.js server-entry.js 五.配置客户端打包和服务端打包 六.配置运行脚本 七.服务端配置 七.通过json配置createBundleRenderer方法 八.集成VueRouter 配置入口文件 配置组件信息 防止刷新页面不存在 保证异步路由加载完成 十.集成vuex配置 在后端更新vuex 在浏览器运行时

  • 详解React Native与IOS端之间的交互

    前置准备 首先最好了解一点关于 oc 的语法知识 1.创建声明文件nativeModule.h #import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> @interface nativeModule : NSObject <RCTBridgeModule> @end 2.创建文件nativeModule.m #import <Foundation/Foundation.h> #i

  • 详解vue服务端渲染(SSR)初探

    前言 首先来讲一下服务端渲染,直白的说就是在服务端拿数据进行解析渲染,直接生成html片段返回给前端.具体用法也有很多种比如: 传统的服务端模板引擎渲染整个页面 服务渲染生成htmll代码块, 前端 AJAX 获取然后js动态添加 服务端渲染的优劣 首先是seo问题,前端动态渲染的内容是不能被抓取到的,而使用服务端渲染就可以解决这个问题.还有就是首屏加载过慢这种问题,比如在SPA中,打开首页需要初始加载很多资源,这时考虑在首屏使用服务端渲染,也是一种折中的优化方案.但是使用SSR时,势必会增加服

  • 详解vue使用$http服务端收不到参数

    老夫子我正在憋方案书,听到身后传来细软的声音:"李哥,我这有个Bug调了很长时间了,您能帮我看一下吗?".说这话的是我的好朋友,公司新来的前端小妹伊万卡.我起身向她走去,看到因长时间调试Bug漂亮的脸蛋上泛起的红晕,原来人会变的温柔,一点都不像我. " 我使用vue中的http方法异步删除一个图片,后端怎么也接收不到我发的参数,同时还报个500. " 听完伊万卡小妹描述的这个Bug临床表现,根据我多年的行医经验,已大体猜出病灶所在.但我并不想这么快结束这次义诊,你们

  • 详解使用Nuxt.js快速搭建服务端渲染(SSR)应用

    安装 nuxt.js Nuxt.js 官方提功了两种方法来进行项目的初始化,一种是使用Nuxt.js团队的脚手架工具 create-nuxt-app ,一种是根据自己的需求自由配置 使用脚手架适合新手,对 nodejs 后台框架有所了解:按照自己需求自由配置,需要对如何配置 webpack 以及 nodejs 后台框架有所了解. 两种方式比较下就是原生和插件的区别. 使用脚手架安装 需要有 nodejs 或者yarn环境,推荐使用 vscode 自带的控制台输入命令行命令进行操作 在有了环境之后

  • 详解React 的几种条件渲染以及选择

    对于一个展示页面来讲, 通常有好几种展示状态(以列表页为例): 数据为空, 空页面 取数据时发生错误, 错误页面 数据正常 加载状态 针对以上三种情况, react渲染列表的时候要正确判断并渲染出相应的视图, 也就是条件渲染. 不同于vue的v-if, v-show等框架提供的api, react的条件渲染都是js原生的再加上一点点的hack. 比如react文档提到的. if/else, && 和三目等等. 当然上面的都是常用的一些方法, 但是也存在着各种问题, 比如条件分支过多的的事时

随机推荐