草庐IT

以工作中的项目为例实践前端常见十大性能优化方法

Yolanda_2022 2023-06-12 原文

一、记录优化前的初始状态

1.查看首屏初次渲染加载时间

打开控制面板,勾选 停用缓存 后,刷新项目首屏,得到首屏初次渲染的资源加载情况。
标记1处:
DomContentLoaded:dom内容加载完毕的时间为11.64秒,它对应着瀑布栏下蓝色的那条竖线。
标记2处:
加载时间:页面上所有的资源(图片,音频,视频等)被加载完成的时间为11.66秒,对应着瀑布栏下红色的那条竖线。
我们主要关注首屏的加载时间这个指标,这里11.66秒过长了,那么我们重点关注下完成加载时间最晚的几个资源文件,也就是标记3和标记4处的资源文件。
观察标记4处的瀑布图,我们发现加载时间最长的是vendor.js这个文件,这是我们项目的打包出来的库文件,如果我们能把其中体积较大且首屏用不上的第三方包从里面拆分出来,等到需要用的时候再去加载或者在首屏加载完成后页面空闲的时候再去加载,那么首屏的加载时间会缩短一些。同理,加载时间较长的index.js文件是我们的打包的业务代码文件,如果我们能利用路由懒加载把其他的路由文件拆分出来,那么首屏也会更快的渲染出来。
标记3处是antd和icons的cdn资源的加载,因为这块的资源是无法改动的,所以它可能决定了我们首屏加载时间优化的上限,我们最好的结果可能就是在antd.js文件加载完成的时候完成首屏的加载,那么这里大概就是根据第二张图里antd.js的进入队列的时间和antd.js加载的时间加总,得到七八秒是我们优化的上限。

2.查看二次加载时间

我们取消 停用缓存 的勾选,再次刷新页面,查看二次渲染的情况。因为二次渲染利用了缓存,所以加载时间为856毫秒,结果是比较好的。

3.Lighthouse评分情况

二、性能优化(上)(访问效率)

(一)去除console.log

1.配置方法

// webpack.prod.js

const TerserJSPlugin = require('terser-webpack-plugin')

module.exports ={
  //...
 optimization: {
    minimizer: [ 
      new TerserJSPlugin({
        //...
        terserOptions: { 
          compress: { 
            drop_console: true 
          } 
        }
      }
   )]
  //...
}

2.优化结果

我们利用webpack-bundle-analyzer插件查看去除前后业务代码文件index.js的体积变化,确实小了一些。
去除前:

去除后:

(二)拆分首屏不需要的vendor库文件

利用webpack-bundle-analyzer插件分析发现vendor.js文件中最大的的三个包分别是monaco-editor,antd,以及
涂鸦公共组件库tuya-fe.
(PS:笔者首屏分析中说项目引入了antd的cdn,结果在vendor里也发现了antd,确认了下cdn和vendor里的antd版本是一致的,那么vendor这里的antd是重复打包了,于是笔者在webpack的external节点配置中把antd排除在打包范围之外)

因为monaco-editor和tuya-fe首屏是用不到的体积又很大,下面我们对monaco-editor和tuya-fe进行拆包。

1.配置方法

//webpack.prod.js

optimization: {
     //....
    splitChunks: {
      chunks: 'all',
      // 缓存分组
      cacheGroups: {
        // 拆分monaco-editor
        monacoEditor: {
          chunks: 'async',
          name: 'chunk-monaco-editor',
          priority: 22,
          test: /[\/]node_modules[\/]monaco-editor[\/]/,
          enforce: true,
          reuseExistingChunk: true,
        },
        // 拆分tuya-fe
        tuyaComponent: {
          chunks: 'async',
          name: 'chunk-tuya-component',
          priority: 12,
          test: /[\/]node_modules[\/]@tuya-fe[\/]galaxy-public-components[\/]/,
          enforce: true,
          reuseExistingChunk: true,
        },
        vendor: {
          name: 'vendor', // chunk 名称
          priority: 1, 
          test: /[\/]node_modules[\/]/,
          minSize: 0, // 大小限制
          chunks: 'all',
          minChunks: 1, // 最少复用过几次
        },
        // 公共的模块
        common: {
          name: 'common', // chunk 名称
          priority: 0, // 优先级
          minSize: 0, // 公共模块的大小限制
          minChunks: 2, // 公共模块最少复用过几次
        },
      },
    },
   //...
  },

2.优化效果

vendor.js拆分前

vendor.js拆分后:

(三)路由懒加载&异步路由合并&异步路由prefetch

异步路由prefetch:
我们希望使用魔法注释使得异步路由文件能够在首屏加载完成后的浏览器空闲时间去下载,这样就能使得其他路由的访问更快一些(由于首屏请求过该路由文件,等到访问这个路由的时候,直接从预缓存里去获取这个文件资源)

异步路由合并:
由于除了首屏以外的每个路由文件体积都很小,如果一个个拆分出来,http请求会过多,那么我们通过给多个路由命名相同的webpackChunkName的方式,来合并多个路由文件。

1.配置方法

// router-view.tsx


import { Suspense } from 'react'

<Router>
      <Suspense fallback={<div>loading...<div/>}>
        <Switch>{routeRender(routes)}</Switch>
      </Suspense>
</Router>

// router.ts

import { lazy } from 'react'
const routes: RouteItem[] =[
{
  {
    key: 'addVirtualDevice',
    title: '创建设备',
    path: '/virtual/device/create',
    component: lazy(() =>
      import(
        /* webpackChunkName: "chunk-other-pages", webpackPrefetch: true */ '@pages/virtual/create-device'
      ),
    ),
  },
  {
    title: '版本信息列表',
    path: '/virtual/firmware/version/management',
    component: lazy(() =>
      import(
        /* webpackChunkName: "chunk-other-pages", webpackPrefetch: true */ '@pages/virtual/firmware-list/firmware-version-management'
      ),
    ),
    hide: true,
  },
  
 ]

2.优化效果

路由拆分前:

路由拆分后:

三、性能优化(上)优化效果总结

1.查看首屏初次渲染时间

加载时间从之前的11.66秒,降低至6.32秒。
如下图标记2处,我们从index.js业务代码文件中所拆分出来的异步路由会在首屏加载完成后,浏览器空闲的时候去加载,那么就使得index.js包含首屏代码的文件体积更小,能尽快的加载完首屏内容。
同时,在下图标记1处,我们看到通过实施上面所说的所有优化方法后,加载完成的最后一个资源文件是antd.js的cdn外链资源,因为该资源的不可改动,所以我们对首屏加载时间的优化基本是达到了优化的上限了。

2.关于异步路由prefetch的效果展示

如下图,我们可以看到,prefetch的作用是,当访问到非首屏路由时,因为在首屏预加载过该路由文件,所以直接从预提取缓存里获取该路由文件,所花时间仅2毫秒。

3.查看二次加载时间

二次加载的时间从856ms降低至了818ms.

4.lighthouse评分:

评分从之前的75分提升至了91分。

四、性能优化(中)(打包构建速度,开发效率)

(一)并行压缩JS

TerserWebpackPlugin支持多进程方式执行代码压缩,能提高项目构建速度。
插件 TerserWebpackPlugin 默认已开启并行压缩能力,通常情况下保持默认配置即 parallel = true 即可获得最佳的性能收益

1.配置方法

const TerserJSPlugin = require('terser-webpack-plugin') 
module.exports ={
  //...
optimization: {
    // 并行压缩 js
    minimizer: [ 
      new TerserJSPlugin()
    ],
  }
  //...
}

2.优化效果

如下图,我们可以看到使用并行压缩后,生产环境下的项目构建速度是从23.15s降低到了12.67s.
不使用并行压缩,运行yarn run build的结果:

使用并行压缩,运行yarn run build的结果:

(二)no-parse

默认情况下无论我们导入的模块(库)是否依赖于其它模块(库), 都会去分析它的依赖关系但是对于一些独立的模块(库)而言, 其根本不存在依赖关系, 但是webpack还是会去分析它的依赖关系这样就大大降低了我们打包的速度.
所以对于一些独立的模块(库), 我们可以提前告诉webpack不要去分析它的依赖关系这样就可以提升我们的打包速度.

我们项目中使用到了lodash这个库,因为这个库是没有引入其他的包的,那么我们就可以告诉webapck不用分析lodash的依赖关系。(独立的库常见的有jquery,lodash)

1.配置方法

module: {
    noParse:'/lodash/'
}

2.优化效果

笔者实践后翻车了,不论是开发环境还是生产环境下项目构建速度都没有明显提升,有的时候反而更慢了。
笔者后来检查了下项目中引入的lodash,发现整个项目只按需引入了debounce这一个函数,可能是因为引入的lodash过小,所以没有明显的变化。
笔者将按需引入改成全部引入后,才发现有了较为明显的速度提升。
如下全部引入lodash的情况下,使用noParse,开发环境下构建速度从4917ms提升到了4772ms

如下全部引入lodash的情况下,使用noParse,生产环境下构建速度从16.94s提升到了16.43s


根据项目的实际情况,优化的实际效果对优化方法做取舍,那么笔者就不使用no-parse优化项目。

(三)热更新

就是你在页面用户交互(输入框输入文字,下拉框进行了选择等等)写了些东西后,然后又改了文件,devServer
重新打包,导致页面刷新,交互的东西没有了,那如果希望交互的东西还在,改的东西能更新到页面上,而且页面不刷新,就需要用到热更新。
热更新插件是webpack内置插件HotModuleReplacementPlugin。
但是笔者在实践过程中,发现即便不配置热更新,css模块的热更新也是实现了的。
经过查阅得知,对于css模块而言, 在css-loader中已经帮我们实现了热更新, 只要css代码被修改就会立即更新。
JS模块热更新的实现是难点,笔者之前按照webpack官网中的热更新配置去配置项目,但是并没有起到作用,经查阅得知,原生js的热更新配置和react+ts的热更新配置是不同的,官网上是对原生js的配置。

1.配置方法(react+ts的JS模块热更新配置)

文章参考:
https://www.codeleading.com/article/16042772093/
https://github.com/gaearon/react-hot-loader
影子设备项目配置方法
// app.tsx

import { hot } from 'react-hot-loader/root'

export default hot(App)

//src/index.tsx


if (ENV === 'local') {
  const realModule = module as any
  if (realModule.hot) {
    realModule.hot.accept(() => {
      ReactDOM.render(
        <>
          <WrapperApp onGlobalStateChange={null} />
        </>,
        document.querySelector('#root'),
      )
    })
  }
}

//.barbelrc

{
  "plugins": [
    "react-hot-loader/babel"
   ]
  ]
}

// webpack.dev.js


module.exports = {
 devServer: {
     hot: true
 },
 plugins: [
    new webpack.HotModuleReplacementPlugin(),
  ],
}

2.优化效果

(1)配置前
未配置的时候,开发环境下,我们在输入框输入复制两个字

然后我们更改页面标题,添加‘热更新’字样,保存后页面刷新了,输入框里的内容被清空了。

(2) 配置后
开发环境下,当我们修改页面标题增加了‘热更新’字样保存后,发现输入框里的内容还在,js模块热更新配置成功。

五、性能优化(下)(不适用于libra发布平台)

以下所要实现的四种优化方法笔者没有集成到项目中去,因为gzip,http2和缓存策略这三个优化方法,libra发布平台已经自动帮我们实现了,预渲染因为libra发布平台的项目里的全局变量插入是在项目build之后的流程原因,以及做预渲染的话,项目里引入的CDN需要在build之前存在,而libra上传cdn是在项目build之后,所以这个方法并不适用于我们涂鸦的libra发布平台。

优化前的准备工作(使用nginx部署影子设备项目):

前端项目部署nginx服务器

(一)Gzip

开启gzip压缩可以降低网络传输文件大小,有效的加速网页内容的加载。

1.配置方法

nginx.conf文件添加如下配置:

http {
  gzip  on;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

2.优化效果

开启gzip压缩前
控制台网络面板,大小这一栏显示的是资源文件的网络传输大小,将鼠标悬浮在其上,显示的resource size才是资源本身的大小。
我们可以通过Content-Encoding这一栏查看是否是gzip压缩。

开启gzip压缩后:
我们通过比对vendor.js文件,可以看到开启gzip后,文件网络传输大小从957kB降低到了308kB,是原来的三分之一都不到,文件加载时间也从10.08秒降低到了3.63秒。
整体的首屏的加载时间也从13.28秒降低到了6.76秒。

(二)HTTP2

http1.1和http2相比,http2有哪些优势?
http2的优势:
1.二进制传输
2.请求和响应多路复用
https是我们去使用http2的必须的条件。我们只能在https的情况下,才能去开启http2.

1.配置https服务器方法

前端项目部署nginx服务器

2.优化效果

结果http1.1和http2首屏加载时间差不多。
lighthouse评分的话http2的分数更高一些。

(三)缓存优化

(1)强缓存(Cache-Control, disk cache)

如果资源没有过期,不用跟服务器进行通信,直接使用本地缓存的资源。

强缓存是在响应头中设置cache-control来控制强缓存的逻辑,比如Cache-Control:max-age=31536000(单位是秒)。只要根据响应头里date和cache-control:max-age加总计算得到资源到期时间,比对再次获取资源的当前时间,只要资源没有过期,就命中强缓存。

Cache-Control的值的设定
max-age 设置缓存的过期时间
no-cache 不用本地的强制缓存,正常的向服务端请求,服务端怎么处理我们不管(禁用强缓存,可用协商缓存)
no-store不用本地缓存,也不用服务端的缓存措施,让服务端把资源重新返回一次,这个更彻底(强缓存和协商缓存都禁用)

private只能允许最终用户左右缓存
public允许中间路由,中间代理做缓存
ngix配置强缓存且禁用协商缓存的方法:

初次请求:
date为初次请求的时间

第二次请求(资源未过期的情况):
笔者在八点零六分刷新页面,再次去获取vendor.js资源,因为根据date和cache-control:max-age加总计算,
发现资源还没有到期,所以这个时候命中了强缓存(从状态代码可以看出来,显示内存缓存),而且压根也没有向服务端发起请求(从请求表头显示预配表头可以看出来)

二次请求(资源过期的情况):
资源过期的话,界面和初次请求是一样的,向服务器发起了新的请求,只是响应头里的date会更新为这次新的请求的时间。之后获取资源就根据最近一次向服务端请求资源的时间(date)加总cache-control:max-age的时间得到资源到期时间,比对当前获取资源的时间,没到期就命中强缓存,到期了就重新向服务端发起请求,响应头date会更新,以此循环往复。

(2)协商缓存(Last-Modified和Etag,304状态码)

客户端向服务端询问,资源有无变化,如果服务端检查(判断客户端资源是否和服务端的一样)后发现资源没变,服务端告诉客户端,你可以用你本地的缓存,不用我再给你,返回的状态码是304。如果服务端发现不一样,返回200和新的资源。


使用Etag资源标识做协商缓存

使用Last-Modified做协商缓存


请求示例可以看到协商缓存能减小资源网络传输大小。

nginx配置禁用强缓存开启协商缓存

初次请求(分开截了两张图)


二次请求

(3)协商缓存和强缓存综合流程(优先级:强缓存>etag>Last-Modified,三者可以同时设置)

(4)项目缓存优化策略

1.对于html文件(禁用强缓存):
单页应用只有一个html文件作为唯一的入口,所有的资源都是通过这个html文件去进行后续的加载的,如果资源更新了,我们希望html缓存这个时候过期,这样用户才不会是拿到旧的文件。如果html被强缓存了,那么它始终都是拿到的旧的js,css等资源。
对html文件,我们使用协商缓存或者no-store完全不缓存都是可以的。
nginx配置(index.html完全不缓存):

配置效果展示:
如下,因为完全禁用了缓存,每次都是去服务端重新获取资源,返回状态码200。

2.对js,css,图片等静态资源文件(contenthash+强缓存)
(1)webpack配置
配置output的js,css等静态资源文件为文件名称加上内容的hash值,一旦内容发生了变化, 内容的hash值就会发生变化, 文件的名称也会发生变化一旦文件的名称发生了变化, 浏览器就会自动去加载新打包的文件.
内容变化hash值才变化,这样可以避免更新发布文件后,因为名字没变,使用缓存,结果还是显示旧的页面内容;
内容不变hash值不变,那么就可以利用缓存,加载资源速度更快;
(2)缓存配置
因为文件内容改变,文件名会改变,文件内容不变,文件名不变,所以我们可以设置强缓存。
nginx配置

配置效果:
浏览器自动根据七天算出来是604800秒,自动添加上了cache-control:max-age

(5)注意事项

笔者实践中发现懒加载路由文件的强缓存在谷歌正常模式下是不生效的,无痕模式或者用火狐浏览器才能生效。

(四)预渲染

在打包的过程中将我们单页应用的页面提前渲染。这样可以加快首屏加载的速度。

1.配置方法

(1)安装react-snap插件
(2)package.json文件

 "scripts": {
    "postbuild":"react-snap",
  },
  "reactSnap": {
    "source": "dist",
    "minifyHtml": {
      "collapseWhitespace": false,
      "removeComments": false
    }
  },

(3)修改项目内容
1.将项目需要的全局变量插入index.html文件中,如影子设备需要的变量是ENV和REGION
2.将webpack中publicPath配置为’/',而不是cdn前缀,因为预渲染的时候发现引入的js不存在,会报错。
或者我们也可以先发布日常,将js,css静态资源上传cdn后,再进行build预渲染操作。
3.修改index.js文件

import ReactDOM, { hydrate } from 'react-dom'

function render(props) {
  const { container, onGlobalStateChange } = props
  // ReactDOM.render(
  //   <>
  //     <WrapperApp onGlobalStateChange={onGlobalStateChange} />
  //   </>,
  //   container
  //     ? container.querySelector('#root')
  //     : document.querySelector('#root'),
  // )
  const rootElement = document.querySelector('#root')
  if (rootElement.hasChildNodes()) {
    hydrate(
      <WrapperApp onGlobalStateChange={onGlobalStateChange} />,
      container ? container.querySelector('#root') : rootElement,
    )
  } else {
    ReactDOM.render(
      <WrapperApp onGlobalStateChange={onGlobalStateChange} />,
      container ? container.querySelector('#root') : rootElement,
    )
  }
}
if (!window.__POWERED_BY_QIANKUN__) {
  render({})
}

4.将首页的接口请求进行条件判断(在预渲染的时候不去请求)
因为预渲染的时候会完整执行首页的内容,包括请求接口,发现接口请求不到,就会报错。

(4)执行预渲染
执行yarn run build即可,build完成后就会自行执行react-snap了。

2.优化效果

我们看到打包出来的index.html文件里是有内容的,就是影子设备首页的内容。

六、优化后lighthouse评分情况

综合以上(性能优化(上)和性能优化(下)中的提高访问效率的优化手段)lighthouse评分结果如下:
将评分从91分提升到99分的是预渲染的作用,因为优化(下)前三个都是libra平台实现过了,是体现在91分里的。

七、参考资料

慕课网: 前端性能优化企业级解决方案 6大角度+大厂视野
对应课程笔记: 聊一聊前端性能优化

有关以工作中的项目为例实践前端常见十大性能优化方法的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  5. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  7. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  8. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  9. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  10. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

随机推荐