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

前端之家收集整理的这篇文章主要介绍了详解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:

const Home = () =>
Home
;
const Hello = () =>
Hello
;

const App = () => {
return (

) }

ReactDOM.render(,document.getElementById('app'))

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

用React-Loadable来时间代码拆分

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

然后在你的 javascript中:

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

//然后在你的路由中使用loadable包装过的组件:

很简单吧,我们只需要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服务器:

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

const app = new Koa();
const router = new Router();
router.get('*',async (ctx) => {
ctx.body = `
<!DOCTYPE html>
<html lang="en">

<Meta charset="UTF-8"> React SSR@H_<a href="/tag/502/" target="_blank" class="keywords">502</a>_73@ </head> <body> <div id="app"></div> <script type="text/javascript" src="/bundle.js"></script> </body> </html> `; }); <p>app.use(router.routes());<br /> app.listen(3000,'0.0.0.0');</p> </pre> </div> <p>上面<code>*</code>路由代表任意的url进来我们都默认渲染这个html,<a href="/tag/baokuo/" target="_blank" class="keywords">包括</a>html中打包出来的js,你也可以用一些服务端模板引擎(如:<a target="_blank" href="https://github.com/mozilla/nunjucks" rel="external nofollow" >nunjucks</a>)来直接渲染html<a href="/tag/wenjian/" target="_blank" class="keywords">文件</a>,在webpack打包时通过<code>html-webpack-plugin</code>来<a href="/tag/zidong/" target="_blank" class="keywords">自动</a>插入打包出来的js/css资源路径。 </p> <p>OK,我们的简易koa server好了,接下来我们开始编写React SSR的入口<a href="/tag/wenjian/" target="_blank" class="keywords">文件</a><code>AppSSR.js</code>,这里我们需要使用StaticRouter来代替之前的<code>BrowserRouter</code>,因为在服务端,路由是静态的,用<code>BrowserRouter</code>的话是不起作用的,后面还会做一些配置来使得<code>react-loadable</code>运行在服务端。 </p> <p><a href="/tag/tishi/" target="_blank" class="keywords">提示</a>: 你可以把整个node端的<a href="/tag/daima/" target="_blank" class="keywords">代码</a>用ES6/JSX风格编写,而不是部分commonjs,部分JSX,但这样的话你需要用webpack把整个服务端的<a href="/tag/daima/" target="_blank" class="keywords">代码</a>编译成commonjs风格,才能使得它运行在node环境中,这里的话我们把React SSR的<a href="/tag/daima/" target="_blank" class="keywords">代码</a>单独抽出去,然后在普通的node<a href="/tag/daima/" target="_blank" class="keywords">代码</a>里去require它。因为可能在一个现有的项目中,之前都是commonjs的风格,把以前的node<a href="/tag/daima/" target="_blank" class="keywords">代码</a>一次性转成ES6的话成本有点大,但是可以后期一步步的再迁移过去 </p> <p>OK,现在在你的 AppSRR.js中: </p> <div class="jb51code"> <pre class="brush: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'; <p>//这里吧react-router的路由设置抽出去,使得在浏览器跟服务端可以共用<br /> //下面也会讲到...<br /> import AppRoutes from 'src/AppRoutes';</p> <p>//这里我们创建一个简单的class,暴露一些<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>出去,然后在koa路由里去<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>来实现服务端渲染<br /> class SSR {<br /> //koa 路由里会<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>这个<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a><br /> render(url,data) {<br /> let modules = [];<br /> const context = {};<br /> const html = ReactDOMServer.renderToString(<br /> <Loadable.Capture report={moduleName => modules.push(moduleName)}></p> <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`; }); } <p>static preloadAll() {<br /> return Loadable.preloadAll();<br /> }<br /> }</p> <p>export default SSR;</p> </pre> </div> <p>当编译这个文件的时候,在webpack配置里使用<code>target: "node"</code> 和<code> externals</code>,并且在你的打包前端app的webpack配置中,需要加入react-loadable的插件,app的打包需要在ssr打包之前运行,不然拿不到react-loadable需要的各组件信息,先来看app的打包: </p> <div class="jb51code"> <pre class="brush:js;"> //webpack.config.dev.js,app bundle const ReactLoadablePlugin = require('react-loadable/webpack') .ReactLoadablePlugin; <p>module.exports = {<br /> //...<br /> plugins: [<br /> //...<br /> new ReactLoadablePlugin({ filename: './build/react-loadable.json',}),]<br /> }</p> </pre> </div> <p>在.babelrc中加入loadable plugin: </p> <div class="jb51code"> <pre class="brush:plain;"> { "plugins": [ "syntax-dynamic-import","react-loadable/babel",["import-inspector",{ "serverSideRequirePath": true }] ] } </pre> </div> <p>上面的配置会让<code>react-loadable</code>知道哪些组件最终在服务端被渲染了,然后直接插入到html script标签中,并在前端初始化时把SSR过的组件考虑在内,避免重复加载,下面是SSR的打包: </p> <div class="jb51code"> <pre class="brush:js;"> //webpack.ssr.js const nodeExternals = require('webpack-node-externals'); <p>module.exports = {<br /> //...<br /> target: 'node',output: {<br /> path: 'build/node',filename: 'ssr.js',libraryExport: 'default',libraryTarget: 'commonjs2',},//避免把node_modules里的库都打包进去,此ssr js会直接运行在node端,<br /> //所以不需要打包进最终的文件中,运行时会自动从node_modules里加载<br /> externals: [nodeExternals()],//...<br /> }</p> </pre> </div> <p>然后在<code>koa app.js,require</code>它,并且调用SSR的方法: </p> <div class="jb51code"> <pre class="brush:js;"> //...koa app.js //build出来的ssr.js const SSR = require('./build/node/ssr'); //preload all components on server side,服务端没有动态加载各个组件,提前先加载好 SSR.preloadAll(); <p>//实例化一个SSR对象<br /> const s = new SSR();</p> <p>router.get('*',async (ctx) => {<br /> //根据路由,渲染不同的页面组件<br /> const rendered = s.render(ctx.url);</p> <p>const html = `<br /> <!DOCTYPE html><br /> <html lang="en"></p> <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; }); //... </pre> </div> <p>以上是个简单的实现React SSR到koa web server,为了使<code>react-loadable</code>知道哪些组件在服务端渲染了,<code>rendered</code>里面的<code>scripts</code>数组里面包含了SSR过的组件组成的各个script标签,里面调用了<code>SSR#generateBundleScripts()</code>方法,在插入时需要确保这些script标签在<code>runtime.js</code>之后((通过 <code>CommonsChunkPlugin </code>来抽出来)),并且在app bundle之前(也就是初始化的时候应该已经知道之前的哪些组件已经渲染过了)。更多<code>react-loadable</code>服务端支持,参考这里. </p> <p>上面我们还把react-router的路由都单独抽出去了,使得它可以运行在浏览器跟服务端,以下是<code>AppRoutes</code>组件: </p> <div class="jb51code"> <pre class="brush:js;"> //AppRoutes.js import Loadable from 'react-loadable'; //... <p>const AsyncHello = Loadable({<br /> loading: <div>loading...</div>,loader: () => import('./Hello'),})</p> <p>function AppRoutes(props) {</p> <Switch> <Route exact path="/hello" component={AsyncHello} /> <Route path="/" component={Home} /> </Switch> } <p>export default AppRoutes</p> <p>//然后在 App.js 入口中<br /> import AppRoutes from './AppRoutes';<br /> // ...<br /> export default () => {<br /> return (</p> <Router> <AppRoutes/> </Router> ) } </pre> </div> <p><h3>服务端渲染的初始状态</h3> </p> <p>目前为止,我们已经创建了一个React SPA,并且能在浏览器端跟服务端共同运行🍺,社区称之为<code>universal app </code>或者 <code>isomophic app</code>。但是我们现在的app还有一个遗留问题,一般来说我们app的数据或者状态都需要通过远端的api来异步获取,拿到数据后我们才能开始渲染组件,服务端SSR也是一样,我们要动态的获取初始数据,然后才能扔给React去做SSR,然后在浏览器端我们还要初始化就能同步获取这些SSR时的初始化数据,避免浏览器端初始化时又重新获取了一遍。 </p> <p>下面我们简单从github获取一些项目的信息作为页面初始化的数据,在koa的<code>app.js</code>中: </p> <div class="jb51code"> <pre class="brush:js;"> //... const fetch = require('isomorphic-fetch'); <p>router.get('*',async (ctx) => {<br /> //fetch branch info from github<br /> const api = '<a href="https://api.github.com/repos/jasonboy/wechat-jssdk/branches">https://api.github.com/repos/jasonboy/wechat-jssdk/branches</a>';<br /> const data = await fetch(api).then(res => res.json());</p> <p>//传入初始化数据<br /> const rendered = s.render(ctx.url,data);</p> <p>const html = `<br /> <!DOCTYPE html><br /> <html lang="en"></p> <head> <meta charset="UTF-8"> </head> <body> <div id="app">${rendered.html}</div> <pre><code><script type="text/javascript"&gt;window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script> <script type="text/javascript" src="/runtime.js"&gt;</script> ${rendered.scripts.join()} <script type="text/javascript" src="/app.js"&gt;</script></code></pre> </body> </html> `; ctx.body = html; }); </pre> </div> <p>然后在你的<code>Hello</code>组件中,你需要<code>checkwindow</code>里面(或者在App入口中统一判断,然后通过props传到子组件中)是否存在<code>window.__INITIAL_DATA__,</code>有的话直接用来当做初始数据,没有的话我们在<code>componentDidMount</code>生命周期函数中再去来数据: </p> <div class="jb51code"> <pre class="brush:js;"> export default class Hello extends React.Component { constructor(props) { super(props); <p>this.state = {<br /> //这里直接判断window,如果是父组件传入的话,通过props判断<br /> github: window.__INITIAL_DATA__ || [],};<br /> }</p> <p>componentDidMount() {<br /> //判断没有数据的话,再去请求数据<br /> //请求数据的方法也可以抽出去,以让浏览器及服务端能统一调用,避免重复写<br /> if (this.state.github.length <= 0) {<br /> fetch('<a href="https://api.github.com/repos/jasonboy/wechat-jssdk/branches">https://api.github.com/repos/jasonboy/wechat-jssdk/branches</a>')<br /> .then(res => res.json())<br /> .then(data => {<br /> this.setState({ github: data });<br /> });<br /> }<br /> }</p> <p>render() {<br /> return (</p> <div> <ul> {this.state.github.map(b => { return <li key={b.name}>{b.name}</li>; })} </ul> </div> ); } } </pre> </div> <p>好了,现在如果页面被服务端渲染过的话,浏览器会拿到所有渲染过的html,包括初始化数据,然后通过这些SSR的内容配合加载的js,再组成一个完整的SPA,就像一个普通的SPA一样,但是我们得到了更好的性能,更好的SEO😎。 </p> <p><h3>React-v16 更新</h3> </p> <p>在React的最新版v16中,SSR的API做了很多的优化,并且提供了新的基于流的API来更好的提升性能,通过streaming api,服务端可以边渲染边把前面渲染好的html发到浏览器,浏览器端也可以提前开始渲染页面而不是等服务端所有组件都渲染完成后才能开始浏览器端的初始化,提升了性能也降低了服务端资源的消耗。还有一个在浏览器端需要注意的是需要使用<code>ReactDOM.hydrate()</code>来代替之前的<code>ReactDOM.render(),</code>更多的更新参考medium文章<a target="_blank" href="https://medium.com/m/global-identity?redirectUrl=https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67" rel="external nofollow" >whats-new-with-server-side-rendering-in-react-16.</a> </p> <p>💖要查看完整的demo,参考<a target="_blank" href="https://github.com/JasonBoy/koa-web-kit" rel="external nofollow" >koa-web-kit</a>,koa-web-kit是一个现代化的基于React/Koa的全栈开发框架,<a href="/tag/baokuo/" target="_blank" class="keywords">包括</a>React SSR<a href="/tag/zhichi/" target="_blank" class="keywords">支持</a>,可以直接用来测试服务端渲染的<a href="/tag/gongneng/" target="_blank" class="keywords">功能</a>😀 </p> <p><h3>结论</h3> </p> <p>好了,以上就是React-SSR + Koa的简单实践,通过SSR,我们既提升了<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>,又很好的满足了<a href="/tag/SEO/" title="SEO">SEO</a>的要求,Best of the Both Worlds🍺。</p> <p><a target="_blank" href="https://blog.lovemily.me/react-server-side-rendering-with-koa/" rel="external nofollow" >English Version</a></p> <p>以上就是本文的全部<a href="/tag/neirong/" target="_blank" class="keywords">内容</a>,希望对大家的学习有所帮助,也希望大家多多<a href="/tag/zhichi/" target="_blank" class="keywords">支持</a>编程之家。</p></div> <div class="topcard-tags"><a href="/tag/react/" class="tag_link" target="_blank">react</a><a href="/tag/reactp/" class="tag_link" target="_blank">react</a><a href="/tag/preact/" class="tag_link" target="_blank">react</a><a href="/tag/preactp/" class="tag_link" target="_blank">react</a><a href="/tag/SSR/" class="tag_link" target="_blank">SSR</a><a href="/tag/fuwuduanxuanran/" class="tag_link" target="_blank">服务端渲染</a></div> <ul class="list-group"> <li class="list-group-item"><a href="/js/32191.html" title="JavaScript+H5实现微信摇一摇功能">上一篇:JavaScript+H5实现微信摇一摇功能</a><a href="/js/32178.html" title="JavaScript轮播停留效果的实现思路" class="text-muted pull-right">下一篇:JavaScript轮播停留效果的实现思路</a> </li> </ul> </div> </div> </div> <!-- row end --> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <ins class="adsbygoogle" style="display:block" data-ad-format="autorelaxed" data-ad-client="ca-pub-4605373693034661" data-ad-slot="9144498553"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></div> </div> </div> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <div class="title"><h1>猜你在找的JavaScript相关文章</h1></div> <div class="list_con"> <a href="/js/997747.html" title="Javascript中的事件冒泡与捕获"><div class="title">Javascript中的事件冒泡与捕获</div> <div class="summary">事件冒泡和事件捕获 起因:今天在封装一个bind函数的时候,发现el.addEventListener函数支...</div> <time class="summary">作者:前端之家 时间:2021-02-22</time> </a> </div> <div class="list_con"> <a href="/js/997746.html" title="搞懂js中小数运算精度问题原因及解决办法"><img class="lazy" src="/images/np.jpg" data-original="/res/2021/02-22/19/e40e1eb184cb2a5d8c5f6c5e730d8e82.png" title="" width="160" height="90" style="float:right;margin-left:30px;display:none;" /><div class="title">搞懂js中小数运算精度问题原因及解决办法</div> <div class="summary">js小数运算会出现精度问题 js number类型 JS 数字类型只有number类型,number类型相当于其...</div> <time class="summary">作者:前端之家 时间:2021-02-22</time> </a> </div> <div class="list_con"> <a href="/js/997744.html" title="搞懂:前端跨域问题JS解决跨域问题VUE代理解决跨域问题原理"><div class="title">搞懂:前端跨域问题JS解决跨域问题VUE代理解决跨域问题原理</div> <div class="summary">什么是跨域 跨域 : 广义的跨域包含一下内容 : 1.资源跳转(链接跳转,重定向跳转,表单提...</div> <time class="summary">作者:前端之家 时间:2021-02-22</time> </a> </div> <div class="list_con"> <a href="/js/997743.html" title="前端对base64编码的理解,原生js实现字符base64编码"><div class="title">前端对base64编码的理解,原生js实现字符base64编码</div> <div class="summary">@ &quot;TOC&quot; 常见对base64的认知(不完全正确) 首先对base64常见的认知,也是...</div> <time class="summary">作者:前端之家 时间:2021-02-22</time> </a> </div> <div class="list_con"> <a href="/js/997742.html" title="搞懂:MVVM模型以及VUE中的数据绑定数据劫持发布订阅模式"><div class="title">搞懂:MVVM模型以及VUE中的数据绑定数据劫持发布订阅模式</div> <div class="summary">搞懂:MVVM模式和Vue中的MVVM模式 MVVM MVVM : 的缩写,说都能直接说出来 :模型, :视图...</div> <time class="summary">作者:前端之家 时间:2021-02-22</time> </a> </div> <div style="border-bottom: 1px solid #f4f4f4;margin-top:20px;"> <ins class="adsbygoogle" style="display:block" data-ad-format="fluid" data-ad-layout-key="-fr-2o+fp-dx-wx" data-ad-client="ca-pub-4605373693034661" data-ad-slot="4561116489"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div><div class="list_con"> <a href="/js/997318.html" title="js判断浏览器是否支持webGL"><div class="title">js判断浏览器是否支持webGL</div> <div class="summary">起因是我之前开发的网页,用到了three.js制作了一个3d的旋转球体效果。 在各种浏览器上运行...</div> <time class="summary">作者:前端之家 时间:2021-02-14</time> </a> </div> <div class="list_con"> <a href="/js/997317.html" title="js判断undefined和null"><div class="title">js判断undefined和null</div> <div class="summary">js判断undefined js判断null js判断null和undefined</div> <time class="summary">作者:前端之家 时间:2021-02-14</time> </a> </div> <div class="list_con"> <a href="/js/997316.html" title="将文字自动转为banner打印形式的工具"><div class="title">将文字自动转为banner打印形式的工具</div> <div class="summary">http://patorjk.com/software/taag/</div> <time class="summary">作者:前端之家 时间:2021-02-14</time> </a> </div> <div class="list_con"> <a href="/js/997315.html" title="聊一聊 bootstrap 的轮播图插件"><div class="title">聊一聊 bootstrap 的轮播图插件</div> <div class="summary">今天做工作的时候,轻车熟路的做完,又用到了bootstrap的轮播图,觉得有必要安利一下这个插...</div> <time class="summary">作者:前端之家 时间:2021-02-14</time> </a> </div> <div class="list_con"> <a href="/js/997314.html" title="js实现图片无缝循环跑马灯"><div class="title">js实现图片无缝循环跑马灯</div> <div class="summary">html 代码 css js代码 function mylsRunHorseLight() { if (mylsTimer != null) { clearIn...</div> <time class="summary">作者:前端之家 时间:2021-02-14</time> </a> </div> <div style="border-bottom: 1px solid #f4f4f4;margin-top:20px;"> <ins class="adsbygoogle" style="display:block" data-ad-format="fluid" data-ad-layout-key="-fr-2o+fp-dx-wx" data-ad-client="ca-pub-4605373693034661" data-ad-slot="4561116489"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div></div> </div> </div> </div> <!-- left end--> <!-- right --> <div class="col-sm-12 col-md-12 col-lg-3"> <!-- row --> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <label class="main-content-label ">编程分类</label> <div class="cate mt-20"><a href="/html/" title="HTML">HTML</a><a href="/html5/" title="HTML5">HTML5</a><a href="/js/" title="JavaScript">JavaScript</a><a href="/css/" title="CSS">CSS</a><a href="/jquery/" title="jQuery">jQuery</a><a href="/bootstrap/" title="Bootstrap">Bootstrap</a><a href="/angularjs/" title="Angularjs">Angularjs</a><a href="/typescript/" title="TypeScript">TypeScript</a><a href="/vue/" title="Vue">Vue</a><a href="/dojo/" title="Dojo">Dojo</a><a href="/json/" title="Json">Json</a><a href="/electron/" title="Electron">Electron</a><a href="/nodejs/" title="Node.js">Node.js</a><a href="/extjs/" title="extjs">extjs</a><a href="/express/" title="Express ">Express </a><a href="/xml/" title="XML">XML</a><a href="/es6/" title="ES6">ES6</a><a href="/ajax/" title="Ajax">Ajax</a><a href="/flash/" title="Flash">Flash</a><a href="/unity/" title="Unity">Unity</a><a href="/react/" title="React">React</a><a href="/flex/" title="Flex">Flex</a><a href="/antdesign/" title="Ant Design">Ant Design</a><a href="/webfrontend/" title="Web前端">Web前端</a><a href="/weapp/" title="微信小程序">微信小程序</a><a href="/wxmp/" title="微信公众号">微信公众号</a><div class="clearfix"></div> </div> </div> </div> </div> <!-- row end --> <!-- row --> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <!-- f2er-rightads --> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-4605373693034661" data-ad-slot="7756441254" data-ad-format="auto" data-full-width-responsive="true"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> </div> <!-- row end --> <!-- row --> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <label class="main-content-label ">最新文章</label> <ul class="n-list"><li><a href="/js/997747.html" title="Javascript中的事件冒泡与捕获" target="_blank">• Javascript中的事件冒泡与</a></li> <li><a href="/js/997746.html" title="搞懂js中小数运算精度问题原因及解决办法" target="_blank">• 搞懂js中小数运算精度问题</a></li> <li><a href="/js/997744.html" title="搞懂:前端跨域问题JS解决跨域问题VUE代理解决跨域问题原理" target="_blank">• 搞懂:前端跨域问题JS解决</a></li> <li><a href="/js/997743.html" title="前端对base64编码的理解,原生js实现字符base64编码" target="_blank">• 前端对base64编码的理解,</a></li> <li><a href="/js/997742.html" title="搞懂:MVVM模型以及VUE中的数据绑定数据劫持发布订阅模式" target="_blank">• 搞懂:MVVM模型以及VUE中的</a></li> <li><a href="/js/997493.html" title="js实现横向跑马灯效果" target="_blank">• js实现横向跑马灯效果</a></li> <li><a href="/js/997318.html" title="js判断浏览器是否支持webGL" target="_blank">• js判断浏览器是否支持webG</a></li> <li><a href="/js/997317.html" title="js判断undefined和null" target="_blank">• js判断undefined和null</a></li> <li><a href="/js/997316.html" title="将文字自动转为banner打印形式的工具" target="_blank">• 将文字自动转为banner打印</a></li> <li><a href="/js/997315.html" title="聊一聊 bootstrap 的轮播图插件" target="_blank">• 聊一聊 bootstrap 的轮播图</a></li> </ul> </div> </div> </div> <!-- row end --> <!-- row --> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <label class="main-content-label ">热门标签 <span class="pull-right tx-12"> <a href="/all" target="_blank">更多 ►</a></span> </label> <div class="topcard-tags"><a href="/tag/guanbiyangao/" title="关闭广告" target="_blank">关闭广告</a><a href="/tag/danduheaders/" title="单独headers" target="_blank">单独headers</a><a href="/tag/fengzhuangdaima/" title="封装代码" target="_blank">封装代码</a><a href="/tag/tishicuowu/" title="提示错误" target="_blank">提示错误</a><a href="/tag/zhengshuzhengze/" title="整数正则" target="_blank">整数正则</a><a href="/tag/fei0kaitou/" title="非0开头" target="_blank">非0开头</a><a href="/tag/tiaoye/" title="跳页" target="_blank">跳页</a><a href="/tag/chuyema/" title="出页码" target="_blank">出页码</a><a href="/tag/antdtable/" title="antd table" target="_blank">antd table</a><a href="/tag/tishiURLweizhuce/" title="提示URL未注册" target="_blank">提示URL未注册</a><a href="/tag/gongzhonghaozhifu/" title="公众号支付" target="_blank">公众号支付</a><a href="/tag/vuehashmoshi/" title="vue hash模式" target="_blank">vue hash模式</a><a href="/tag/iSlider/" title="iSlider" target="_blank">iSlider</a><a href="/tag/chepaijianpan/" title="车牌键盘" target="_blank">车牌键盘</a><a href="/tag/xunhuantupian/" title="循环图片" target="_blank">循环图片</a><a href="/tag/echartsshuangzhexian/" title="echarts 双折线" target="_blank">echarts 双折</a><a href="/tag/zuoyoubuju/" title="左右布局" target="_blank">左右布局</a><a href="/tag/DllPlugin/" title="DllPlugin" target="_blank">DllPlugin</a><a href="/tag/duixiangchuangjian/" title="对象创建" target="_blank">对象创建</a><a href="/tag/daziyouxi/" title="打字游戏" target="_blank">打字游戏</a><a href="/tag/quanxuan/" title="圈选" target="_blank">圈选</a><a href="/tag/lianglan/" title="两栏" target="_blank">两栏</a><a href="/tag/yunhanshu/" title="云函数" target="_blank">云函数</a><a href="/tag/mengban/" title="蒙版" target="_blank">蒙版</a><a href="/tag/ES2020/" title="ES2020" target="_blank">ES2020</a><a href="/tag/chuchuang/" title="橱窗" target="_blank">橱窗</a><a href="/tag/wufenggundonglunbo/" title="无缝滚动轮播" target="_blank">无缝滚动轮播</a><a href="/tag/sekuaipengzhuang/" title="色块碰撞" target="_blank">色块碰撞</a><a href="/tag/zujianxiaohui/" title="组件销毁" target="_blank">组件销毁</a><a href="/tag/wendangcaozuo/" title="文档操作" target="_blank">文档操作</a></div> </div> </div> </div> <!-- row end --> <!-- row --> <div class="row row-sm"> <div class="col-sm-12 col-md-12 col-lg-12"> <div class="card"> <!-- f2er-rightads --> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-4605373693034661" data-ad-slot="7756441254" data-ad-format="auto" data-full-width-responsive="true"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> </div> <!-- row end --> </div> <!-- right end --> </div> </div> <footer id="footer"> <div class="container"> <div class="row hidden-xs"> <dl class="col-sm-6 site-link"> <dt>最近更新</dt><dd><a href="/win11/1005328.html" title="小米手机重装系统价格多少?专业维修服务详解" target="_blank">· 小米手机重装系统价格多少?专业维修服务详解</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005327.html" title="手把手教你重装电脑系统,让你的电脑焕然一新!" target="_blank">· 手把手教你重装电脑系统,让你的电脑焕然一新!</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005326.html" title="教你一步步重装XP系统,让你的电脑重获新生" target="_blank">· 教你一步步重装XP系统,让你的电脑重获新生</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005325.html" title="从备份到上网:一步步教你重装电脑系统" target="_blank">· 从备份到上网:一步步教你重装电脑系统</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005324.html" title="Sony笔记本电脑一键重装系统详细图文教程" target="_blank">· Sony笔记本电脑一键重装系统详细图文教程</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005323.html" title="Lenovo笔记本重装系统超详细教程,小白也能轻松上手" target="_blank">· Lenovo笔记本重装系统超详细教程,小白也能轻松...</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005322.html" title="联想笔记本一键重装Win10系统详细教程" target="_blank">· 联想笔记本一键重装Win10系统详细教程</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005321.html" title="电脑系统故障无需愁,专业维修店帮你重装旧貌换新颜" target="_blank">· 电脑系统故障无需愁,专业维修店帮你重装旧貌换新...</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005320.html" title="联想笔记本一键重装Win10系统图文教程,小白也能轻松搞定!" target="_blank">· 联想笔记本一键重装Win10系统图文教程,小白也能...</a><span class="text-muted pull-right">02-05</span></dd> <dd><a href="/win11/1005319.html" title="笔记本重装系统图文教程:从光盘启动一步到位" target="_blank">· 笔记本重装系统图文教程:从光盘启动一步到位</a><span class="text-muted pull-right">02-05</span></dd> </dl> <dl class="col-sm-4 site-link"> <dt>好站推荐</dt><dd> <a href="https://www.runoob.com" title="菜鸟教程(www.runoob.com)提供了编程的基础技术教程, 介绍了HTML、CSS、Javascript、Python,Java,Ruby,C,PHP , MySQL等各种编程语言的基础知识。 同时本站中也提供了大量的在线实例,通过实例,您可以更好的学习编程。" target="_blank">菜鸟教程</a></dd><dd> <a href="https://www.jb51.cc" title="编程之家(www.jb51.cc)是成立于2017年面向全球中文开发者的技术内容分享平台。提供编程导航、编程问答、编程博文、编程百科、编程教程、编程工具、编程实例等开发者最需要的编程技术内容与开发工具支持,与你一起学习编程,相信编程改变未来!" target="_blank">编程之家</a></dd><dd> <a href="" title="前端之家 f2er.com 前端开发人员所需学习知识手册。" target="_blank">前端之家</a></dd></dl> <dl class="col-sm-2 site-link"> <dt>商务合作</dt> <dd><a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=76874919&site=qq&menu=yes">联系我们</a></dd> </dl> </div> <div class="copyright"> Copyright © 2019 前端之家. 当前版本 V7.0.16<br> <span class="ml5">前端之家 版权所有 <a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow">闽ICP备13020303号-10</a></span> </div> </div> </footer> <script type="text/javascript" src="/js/base.js"></script> </body> </html>