基于React16+Webpack4搭建前端工作流

前言

前端技術發展至今,各種框架技術層出不窮,前后端分離的開發模式也大行其道,如今,前端大多以webapp的形式存在,很多后端的工作都開始拿到前端來做,而后端只需要提供負責請求數據的API接口,前端工作開始變得龐雜,關注點由單維的頁面視效發展成了路由、測試、效率等多個方面,注意力無法聚焦,這直接導致了開發方式的升級換代,本文將以Webpack構建項目工程為切入點,通過工程架構和項目架構,以點帶面地探索一下前端項目的開發流程及其發展趨勢。

webapp架構

在webapp模式中,前端通過后端API請求大量數據,之后完成頁面渲染、頁面跳轉等行為,前端內容多為JS代碼,甚至很少有寫HTML的行為,即使有也有很大可能作為JS代碼的一部分存在,前端變得越來越復雜,這就要求考慮其架構問題。
一般來講,webapp的架構分為工程架構和項目架構。拿工程架構來講,主要有以下幾個方面:通過工程腳手架的搭建來解放生產力(即各種自動行為);通過合理的技術選型來完成項目解決方案;通過制定代碼規范(codelint,editconfig和git commit預處理)來保證團隊合作及項目質量。此外,工程架構需要突出‘可定制性’,并需要預期可能出現的問題并提前準備解決方案;其次是項目架構,主要是通過技術選型、數據解決方案、代碼風格、合理的目錄結構來實現。

為什么選擇Webpack

我們需要一款可定制的自動化工具,使得注意力更加聚焦于業務自身,而不是其他(源碼預處理、自動打包、自動更新頁面顯示、自動處理圖片依賴等),進而提高開發效率。

項目目錄結構

client
--app.js//應用入口
--App.jsx//頁面內容
--sever-entry.js//用于SSR
--template.html
build
--webpack.config.client.js
--webpack.config.sever.js//用于SSR打包配置
sever//web服務,用于SSR
dist //系統生成的文件夾,用于存放打包JS文件
package.json
.babelrc //babel配置文件

Webpack基礎配置

在webpack.config.client.js中最基礎的對象為entry和output,分別為打包文件入口和輸出文件出口。

module.exports = {
    entry:{
      app: path.join(__dirname, '../client/app.js')//使用絕對路徑
    }
    output:{
      filename: '[name].[hash].js', //入口文件只要有更改,hash值便更改,用于瀏覽器緩存
      path: path.join(__dirname, '../dist/'),
      publicPath: '/public' //靜態資源引用時的路徑,用于區分URL是靜態資源還是API請求,為路徑前綴    
    }
}

webpack最基礎的如上,這時使用webpack --config build/webpack.config.client.js即可完成打包。

Webpack loader基礎配置

Webpack的核心是打包,而其靈魂是loader!當項目為react時,需要配置loader使得webpack可以識別JSX

//需要首先安裝babel-loader和babel-core
module:{
  rules: [
    {
      test: /.jsx$/,
      loader: 'babel-loader'
    },
    {
      test: /.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/
    }
  ]
}

配置babel

描述react組件的jsx語法和JS高級語法暫時不能被瀏覽器執行,需要babel進行“翻譯”,固需進行下面的配置:

//首先安裝babel-preset-es2015 babel-preset-es2015-loose babel-preset-react
//在。babelrc中編輯:
{
    "presets": [
        ["es2015", {"loose": true}],
        "react"
    ]
}

此時webpack可以編譯jsx和es6語法了。

Webpack插件機制

安裝瀏覽器打開插件html-webpack-plugin

plugins:[
  new HTMLPlugin({
    template: path.join(__dirname, '../client/template.html')
  }),//自動生成一模板HTML頁面,并根據Webpack配置插入資源
]

以上便完成了最為基礎的基于webpack的ReactWebApp。

Webpack SSR基礎配置

在Webapp開發模式中,頁面內容由瀏覽器渲染,而為了SEO和縮短首屏時間,SSR成為了優化的必選項。React提供了react-dom插件用來解決SSR問題。即在node環境中完成渲染,然后返回給瀏覽器一些可以顯示的HTML內容。

//sever-entry.js中
import React from 'react'
import App from './App.jsx'
export default <App />

上面新建代碼文件(首屏代碼),可為服務端所用,由于代碼無法直接運行在node端,所以下一步還需要Webpack進行服務端打包配置。

target: 'node',
entry:{
      app: path.join(__dirname, '../client/sever-entry.js')
}
output: {
  filename: 'sever-entry.js',//導出文件配置成SSR專用文件
  path: path.join(__dirname, '../dist/'),
  publicPath: '',
  libraryTarget: 'commonjs2'//適用于nodeSSR的模塊打包規范
}

配置好后,前后端分別打包(可用腳本配置),即可生成一個HTML頁面,2個JS文件(前后端各一個),通過express提供中間層SSR服務,通過’react-dom/server‘ 模塊提供的SSR方法進行服務端渲染,即將SSR的內容插入到body中,再將整個頁面返回到瀏覽器

//在server端
const express = require('express')
const ReactSSR = require('react-dom/server')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.join(_dirname, '../dist/index.html'),'ntf-8')
const serverEntry = require('../dist/server-entry').default //此為webpack打包生成的文件
const app = express()
app.use('./public', express.static(path.join(__dirname, '../dist'))) //處理靜態文件
app.get(' * ', function(req, res){
  res.send(template.replace('<app><app/>', ReactSSR.renderToString(serverEntry)))
})
app.listen(1234)

上面為最簡單的SSR,而完整的SSR流程是把SSR的內容替換到模板HTML頁面的<App/>中,再將整個頁面返回到客戶端瀏覽器:這時整個SSR算是完整走通了。

Webpack-dev-server配置

開發過程中,可以使用webpack提供的一些組件來提高開發效率。

const isDev = process.env.NODE_ENV === 'development'//判斷是否為開發環境,通過啟動命令加以區分。一般情況下,dev-server會配置在開發環境下。
//webpack-dev-sever在dist目錄下啟動服務器
devServer :{
  host: '0.0.0.0',//可以通過多種方式訪問
  port: 8888,
  contentBase: path.join(__dirname, '../dist/'),//靜態文件目錄
  hot: true,//需要在react中配置相關熱更新模塊
  overlay: {//顯示錯誤信息
    errors: true
  },
publicPath: '/public',//訪問靜態文件時均要加上這個路徑前綴
historyApiFallback: {
    index: './public/index.html'
  }
}

其中,需要用模塊cross-env來解決跨平臺的環境變量問題。

hot-module-replacement配置

webpack提供的局部無刷新更新插件,需要在.babelrc中配置。

"plugins": ["react-hot-loader/babel"]
//在app.js中
import { AppContainer} from 'react-hot-loader'//熱更新代碼需要被“AppContainer”進行包裹:

const root = document.getElementById('root'))

const render = Component => {
  ReactDOM.hydrate(
    <AppContainer>
      <Component />
    <AppContainer/>,
    root
  )
}

if(module.hot){
  module.hot.accept('./App.jsx', () => {
    const NextApp = require('./App.jsx').default
    render(NextApp)
  })
}

在webpack中添加插件:

publicPath:'/public/'//如果沒有第二個/,會有404錯誤,影響熱更新
plugins:[
  new HTMLPlugin({
    template: path.join(__dirname, '../client/template.html')
  }),//自動生成一模板HTML頁面,并根據Webpack配置插入資源
  new webpackHotModuleReplacementPlugin()//熱更新插件
]

為了使熱更新生效,需要在入口添加新的打包文件:

entry:{
 app: ['react-hot-loader/patch', //熱更新要用到的內容
        path.join(__dirname, '../client/app.js')//使用絕對路徑]
}

此時,修改代碼便可以完成無刷新的熱更新更能。

開發環境下的SSR

在開發環境下配置SSR時,需要使用客戶端的模板文件和服務端的bundle文件,所以在開發環境下的SSR配置重點分為兩塊:由于開發環境下沒有靜態文件生成,不能直接通過路徑獲取,可以在webpack-dev-server中通過網絡請求獲得模板文件;通過webpack和對應配置文件獲得存放于內存中的bundle.js文件內容:

//通過向webpack-dev-sever動態請求模板文件
const getTemplate = () => {
  return new Promise((resolve, reject) => {
    axios.get('http://localhost:8888/public/index.html')
      .then(res => {
        resolve(res.data)
      })
      .catch(reject)
  })
}
//通過webpack API監聽bundle.js文件變化并寫入內存,并以模塊導出,在dev-static.js中 
const NativeModule = require('module')
const vm = require('vm')

const getModuleFromString = (bundle, filename) => {
  const m = { exports: {} }
  const wrapper = NativeModule.wrap(bundle)
  const script = new vm.Script(wrapper, {
    filename: filename,
    displayErrors: true,
  })
  const result = script.runInThisContext()
  result.call(m.imports, m.exports, require, m)
  return m
}

const serverCompiler = webpack(serverConfig)
serverCompiler.outputFileSystem = mfs//webpack配置項,直接寫入內存
let serverBundle
serverCompiler.watch({}, (err, stats) => {//監聽文件變化
  if (err) throw err
  stats = stats.toJson()
  stats.errors.forEach(err => console.error(err))
  stats.warnings.forEach(warn => console.warn(warn))

  const bundlePath = path.join(
    serverConfig.output.path,
    serverConfig.output.filename
  )
  const bundle = mfs.readFileSync(bundlePath, 'utf-8')
  const m = getModuleFromString(bundle, 'server-entry.js')//string內容轉化成模塊
  serverBundle = m.exports.default
})
//最后通過express路由完成SSR
const proxy = require('http-proxy-middleware')//express的請求代理插件

app.use('/public', proxy({targ: 'http://localhost: 8888'}))//處理靜態文件

app.get('*', function (req, res) {
    getTemplate().then(emplate => {
      const content ReactDomSever.rendToString(serverBundle)
      res.send(template.replace('<!--app-->', content))
    })
  })

eslink與editconfig

以上,基本完成了一個webapp項目的工程腳手架的搭建。而為了有利于團隊合作和提高開發效率,有必要用eslink和editconfig來對代碼進行有效規范。

//在clint目錄下新建的.eslintrc文件中
{
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module"
  },
  "extends": "airbnb",
  "rules": {
      "semi": [0]//不寫分號
    }
}

這樣,在每次代碼編譯前,需要先檢查代碼規范,若有規范錯誤,則停止繼續編譯。

//在webpack配置文件中的rules添加如下:
{
     enforce: 'pre',
     test: /.(js|jsx)$/,
     loader: 'eslint-loader',
     exclude: [
     path.resolve(__dirname, '../node_modules')
     ]
}

此外,還需要配置editconfig來規避由于系統差異帶來的某些錯誤。

//在根目錄下新建的.editconfig文件中:
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

目前為止可以優化的點

  1. 首先,webpack配置文件中的公共部分可以提取出來,可使解構更清晰,更利于代碼閱讀。
const webpackMerge = require('webpack-merge') 
const baseConfig = require('./webpack.base') //webpack配置的公共部分

const config = webpackMerge(baseConfig, {
  //非公共配置
})
  1. 處理favicon.ico(相關組件請去Github上搜)。
  2. 通過nodemon監聽文件變化自動重啟服務。
//在根目錄下的nodemon.json文件中配置
{
  "restartable": "rs",
  "ignore": [
    ".git",
    "node_modules/**/node_modules",
    ".eslintrc",
    "client",
    "build"
  ],
  "env": {
    "NODE_ENV": "development"
  },
  "verbose": true,
  "ext": "js"
}

此時,項目工程架構完畢。

項目架構

views //此目錄用于存放項目功能模塊,子目錄的劃分取決于路由
--topic
----index.jsx //每個頁面的入口文件
config //配置,路由,第三方類庫等
--router.js
store // 數據相關
--app-state.js
--store.js
components // 非業務組件和共用組件

配置路由

路由用于頁面跳轉,HTML5中的history api能夠在URL發生變化的時候被JS監聽,進而通過JS向后臺API請求數據,最終呈現新的頁面出來,而在這個過程中history api會阻止瀏覽器刷新頁面的行為,在history api出現之前也可以用hash來完成此操作。
在React項目中,當然要使用react-router。

import React from 'react'
import {
  Route,
  Redirect,
} from 'react-router-dom'

import TopicList from '../views/topic-list/index'
import TopicDetail from '../views/topic-detail/index'
import TestApi from '../views/test/api-test'
//React16中新增數組寫法
export default () => [
  <Route path="/" render={() => <Redirect to="/list" />} exact key="first" />,// exact為精確匹配
  <Route path="/list" component={TopicList} key="list" />,
  <Route path="/detail" component={TopicDetail} key="detail" />,
  <Route path="/test" component={TestApi} key="test" />,
]

上面代碼為<Route />用法,且需要在外層包裹<BrowserRouter />方能正常使用。
此時,當出現如下問題時:


解析出錯

可以在webpack配置文件中添加:

resolve: {
  extensions: ['.js', '.jsx']
}//可以在導入模塊時忽略后綴名

Store配置

眾所周知,React的數據流向只能從父組件向子組件傳遞,顧名思義為單向數據流,但是在實際情況下,有時需要子組件通過回調函數向父組件傳遞數據,當項目龐大,組件嵌套過深時,這種引用回調的方式將變得不可維護,因此,React誕生初期,FB團隊就提出了flux這一數據存儲分發方案,其中數據存儲的地方,我們稱之為Store。flux方案中,不同數據存于不同Store,進而優化誕生了Redux,其所有數據均由一個Store統一管理,基于深拷貝的Store對象一經更改,所有組件立刻重新渲染,而得益于React虛擬DOM的優異性能,使得Redux在性能上變得可行。本文將使用另一種數據解決方案:Mobx。
Mobx相對于Redux更易上手,學習成本更低,與Redux全局重新渲染不同的是,Mobx進行局部刷新。

//在app-state.js中:
import {
  observable,
  computed,
  action,
} from 'mobx'

export default class AppState {
  constructor({ count, name } = { count: 0, name: 'Jokcy' }) {
    this.count = count
    this.name = name
  }
  @observable count
  @observable name
  @computed get msg() {
    return `${this.name} say count is ${this.count}`
  }
  @action add() {
    this.count += 1
  }
  @action changeName(name) {
    this.name = name
  }
  toJson() {
    return {
      count: this.count,
      name: this.name,
    }
  }
}

此外需要向React-Router那樣在最外層包裹,并在頁面入口文件中注入。
注意:當使用裝飾器時,需要babel做相關配置,安裝相關插件。

API代理

Webapp的數據一般由請求后端API接口獲得,一般通過node服務層配合網絡請求庫(如axios)實現后端API代理,具體實現需根據API文檔。
當向后端API通過POST方法請求數據時,為了保證數據對象的“純潔性”,一般需要使用ES6新方法:Object.assign() 傳遞一個新對象出去。

SSR優化

當項目架構加入了react-router和store后,SSR需要根據路由返回的新內容進行渲染,且SSR的渲染內容需要被客戶端所利用,而不是客戶端再次發起一次 HTTP請求,所以需要對SSR進行優化。

//在server-entry.js中,同時這個文件在開發模式下被webpack打包到內存中:
......
import { StaticRouter } from 'react-router-dom'
import { Provider, useStaticRendering } from 'mobx-react'
import { createStoreMap } from './store/store'
// 讓mobx在服務端渲染的時候不會重復數據變換,即方法不被重復調用
useStaticRendering(true)

export default (stores, routerContext, url) => (
  <Provider {...stores}>
    <StaticRouter context={routerContext} location={url}>
      <App />
    </StaticRouter>
  </Provider>
)
//SSR中,需要每次新增一個Store對象,防止每次SSR時,store對象中的數據相互污染
export { createStoreMap }
//store.js中
import AppStateClass from './app-state'

export const AppState = AppStateClass

export default {
  AppState,
}
//下面的函數專門用于SSR
export const createStoreMap = () => {
  return {
    appState: new AppState(),
  }
}

上文中,由于SSR端的入口文件已經改變,所以應在dev-static.js中進行如下修改:

......
const routerContext = { }
const app = serverBundle(createStoreMap(), routerContext, req.url)
......

當路由配置有Redirect的時候,react-router會在routerContext加上“url”屬性,SSR應檢查此屬性是否存在,如存在,在Server端直接跳轉。

//dev-static.js中,SSR代碼后添加:
if(routerContext.url){
  res.status(302).setHeader('Location', routerContext.url) //直接跳轉
  res.end()
  return
}

SSR時有時會異步獲取數據后,再完成SSR的整個過程,React并沒有提供SSR異步方法,這是需要使用工具庫"react-async-bootstrapper":

//dev-static.js中
const app = serverBundle(createStoreMap(), routerContext, req.url)
asyncBootstrapper(app).then(() => {
  //SSR流程
})

然后在store中通過調用“asyncBootstrapper”方法異步獲取數據后,完成余下SSR流程。
此時,Server端已經完成數據渲染,但是client端還需要進行數據同步。

//在app-state.js中有:
......
constructor({ count, name } = { count: 0, name: 'Jokcy' }) {
    this.count = count
    this.name = name
}

toJson() {
    return {
      count: this.count,
      name: this.name,
    }
}

toJson方法使得store對象在SSR后獲取最新的數據,然后只需在client初始化時填充此數據并通過constructor方法初始化store,即可完成前后端數據同步。

//在dec-server.js中:
const getStoreState = (stores) => {
  return Object.keys(stores).reduce((result, storeName) => {
    result[storeName] = stores[storeName].toJson()
    return result
  }, {})
}

state = getStoreState(stores)//獲取SSR后的新數據

最后通過模板引擎(ejs)將數據插入,完成前后端同步。

//在webpack.config.client.js中添加插件:
new HTMLPlugin({
  template: '!!ejs-compiled-loader!' + path.join(__dirname, '../client/server.template.ejs'),
  filename: 'server.ejs'
})
const html = ejs.render(template, {
  appString: content,
  initialState: serialize(state), //object轉為string
})
res.send(html)
const initialState = window.__INITIAL__STATE__ || {}
......
<Provider testMobx={new MobxStore(initialState.testMobx)}>
......

解決Mobx錯誤

Mobx error

上述問題表示多個Mobx實例被啟動,即通過webpack打包生成的前后兩個bundle文件中包含兩個Mobx實例:


Mobx error

此時需要在webpack配置文件進行如下改動:

externals: Object.keys(require('../package.json').dependencies),//node環境下,package.json中的依賴可以通過npm install安裝,所以不用進行打包。

SSR補充

  1. 為了優化SEO,在SSR的時候需要設置Header中的相關信息,如title、meta標簽等:
const Helmet = require('react-helmet').default

asyncBootstrap(app).then(() => {
......
  const helmet = Helmet.rewind()
  onst html = ejs.render(template, {
        ......
        meta: helmet.meta.toString(),
        title: helmet.title.toString(),
        style: helmet.style.toString(),
        link: helmet.link.toString(),
        ......
      })
......
}

對應的打包的SSR信息為:

<Helmet>
  <title>This is topic list</title>
  <meta name="description" content="This is description" />
</Helmet>
  1. SSR時,服務需要請求本地的地址(127.0.0.1),所以需要在webpack.config.server.js添加如下插件:
plugins: [
  new webpack.DefinePlugin({
    'process.env.API_BASE': '"http://127.0.0.1:3333"'
  })
]

打包優化

在之前的webpack.config中,打包后的文件體積過大,因為這個文件中包含了很多庫的全部代碼,客戶端每次請求的時候都要重新加載,這將無法充分利用瀏覽器緩存,加重加載壓力,這時需要修改webpack.config配置,將不變的內容打一個包,將變化的內容打另一個包:

//在正式環境下
config.entry = {
  app: path.join(__dirname, '../client/app.js')
  vendor: [
    'react',
    .......//所有第三方包
  ]
}
config.output.filename = '[name].[chunkhash],js'
config.plugins.push(
  new webpack.optimize.RuntimeChunkPlugin({
    name: 'vendor'//使得app引用的包不再打包進app中
  }),
  new webpack.optimize.RuntimeChunkPlugin({//每次webpack打包生成的代碼放于此文件,并無限壓縮
    name: 'manifast'
  }),
  new webpack.NamedModulesPlugin(),//為異步加載的模塊命名
  new webpack.DefinePlugin({
    'process.env': JSON.stringify('production')
  }),
  new webpack.NamedChunksPlugin((chunk) => {
    if(chunk.name){
      return chunk.name
    }
    return chunk.mapModules(m => path.relative(m.context, m.request)).join('_')
  })
)

完善SSR

此時,開啟SSR服務,系統直接報錯:

錯誤信息

可見此錯誤基本是由SSR端無法對css提供支持所致,由于SSR端無法提供DOM操作,所以無法直接照搬客戶端的webpack配置(即style-loader),不過不用當心,webpack提供了官方的css打包方案:extract-text-webpack-plugin,該插件用于在JS(X)中提取CSS,用法如下:

//webpack4.X安裝此插件時需要加上@next
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader", // 編譯后用什么loader來提取css文件
          use: "css-loader" // 指需要什么樣的loader去編譯文件,這里由于源文件是.css所以選擇css-loader
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("styles.css"),
  ]
}

其次,由于我使用的Material-UI,SSR需要做如下配置(官網可查):

//在項目入口處用 MuiThemeProvider 包裹:
import React from 'react'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
const muiTheme = getMuiTheme({
  userAgent: 'all'
})
import Routes from './config/router'

export default class extends React.Component {
    render() {
        return (
            <MuiThemeProvider muiTheme={muiTheme}>
                <Routes key='routes'/>
            </MuiThemeProvider>
        )
    }
}

SSR處理靜態文件

由于在 webpack 配置中指定了output的publicPath為public,所以靜態資源路徑以/public開頭,因此需要做如下解析:

koa

使用koa-static-plus插件

const koaStaticPlus =require('koa-static-plus')
app.use(koaStaticPlus(path.join(__dirname, '../dist'), {
  pathPrefix: '/public'  //路徑前綴
}))

express

app.use('/public', express.static(path.join(__dirname, '../dist')))

附錄

web開發有哪些常用的網絡優化方式?

  • 合并資源文件,減少HTTP請求
  • 壓縮資源文件
  • 合理利用瀏覽器緩存,通過計算文件內容得出一個哈希值,并通過前后哈希值是否相同來決定用瀏覽器緩存資源還是向后端重新發起請求

2.瀏覽器輸入網址回車后到底發生了什么?

  1. 解析URL
    解析的主要內容如下:
    傳輸協議:https
    服務器:www
    域名:nextsticker.cn
    端口:默認80不顯示
    以及目錄、文件名參數等
  2. DNS解析
    查詢瀏覽器緩存
    檢查系統hosts文件映射
    檢查路由器緩存
    查詢ISP的DNS服務器
    遞歸查詢:從根域名服務器到頂級域名服務器再到極限域名服務器依次搜索對應目標域名的IP(從右往左)
  3. 瀏覽器與服務器建立TCP連接(三次握手)
    第一次握手:客戶端向服務器端發送請求(SYN=1) 等待服務器確認
    第二次握手:服務器收到請求并確認,回復一個指令(SYN=1,ACK=1)
    第三次握手:客戶端收到服務器的回復指令并返回確認(ACK=1)
  4. 傳輸數據
  5. 瀏覽器渲染

Post請求的格式有哪些?

  1. application/x-www-form-urlencoded
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
key=value&testKey=testValue

Content-Type 被指定為 application/x-www-form-urlencoded;提交的數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。大部分服務端語言都對這種方式有很好的支持。很多時候,我們用 Ajax 提交數據時,用的就是這種方式。

  1. multipart/form-data
    我們使用表單上傳文件時,必須讓 form 的 enctyped 等于這個值。
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

首先生成了一個 boundary 用于分割不同的字段,為了避免與正文內容重復,boundary 很長很復雜。然后 Content-Type 里指明了數據是以 mutipart/form-data 來編碼,本次請求的 boundary 是什么內容。消息主體里按照字段個數又分為多個結構類似的部分,每部分都是以 –boundary 開始,緊接著內容描述信息,然后是回車,最后是字段具體內容(文本或二進制)。如果傳輸的是文件,還要包含文件名和文件類型信息。消息主體最后以 –boundary– 標示結束。

  1. application/json
    由于 JSON 規范的流行,除了低版本 IE 之外的各大瀏覽器都原生支持 JSON.stringify,服務端語言也都有處理 JSON 的函數,其更方便提交復雜的結構化數據,特別適合 RESTful 的接口。
  2. text/xml

reactSSR異步獲取數據時請求80端口的解決方案

配置SSR時,通過react組件react-async-bootstrappe異步獲取數據時,意外請求了本地的80端口,可是我的SSR地址為3333端口,API接口地址為3000端口。這是為什么呢?

錯誤信息

因為react-async-bootstrappe提供的方法請求數據時,通過node url parse 去解析/api/admin/all,然后再傳給相應的如 http request模塊,其默認就是80端口,所以將請求發送給了127.0.0.1:80。
至于解決方法,很簡單,將來自react-async-bootstrappe的請求地址配置為http://localhost:3333/api/admin/all,即完整地址即可。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 目錄第1章 webpack簡介 11.1 webpack是什么? 11.2 官網地址 21.3 為什么使用 web...
    lemonzoey閱讀 1,751評論 0 1
  • 本人是去年 7-8月開始準備面試,過五關斬六將,最終在年末抱得網易歸,深深感受到高級前端面試的套路。以下是自己整理...
    前端一菜鳥閱讀 2,632評論 1 43
  • 理解javascript中的MVC MVC模式是軟件工程中一種軟件架構模式,一般把軟件模式分為三部分,模型(Mod...
    深沉的簡單閱讀 510評論 0 0
  • 前天爸爸帶著我和一個伯伯去趕海。 我和伯伯在一個地方撿,爸爸在另一個地方撿。 我們講了好多小螃蟹,小蝦、小魚和一只...
    青青蝸牛尹奕元閱讀 214評論 1 1
  • 你身邊有很多人戴著面具,大部分帶的是笑臉面具。你很難從身邊找到幾個不戴面具的,有,也只不過是你父母,或者是你最好的...
    袁七罪閱讀 74評論 0 0