IE8環(huán)境下Webpack+React全家桶配置總結(jié)

https://github.com/sitorhy/react-webpack-boilerplate

一、IE8基礎(chǔ)兼容問題


1.1、CSS兼容

舊引擎不支持太多CSS3特性,在無法使用Flex布局的情況下盡量使用原始的表格排版代替,使用表格基本上不用考慮兼容性問題。

IE8不支持漸變和透明通道,做全局半透明遮罩的時(shí)候尤其蛋疼,DXImageTransform濾鏡雖然能對容器進(jìn)行著色但不能防止事件穿透,即便是一個(gè)全屏著色的遮罩層其背后的各種控件依然是可以點(diǎn)擊的,建議放棄線性漸變,使用小尺寸PNG圖片作為容器背景元素,設(shè)置background-repeat進(jìn)行平鋪。

1.2、JavaScript兼容

IE8非調(diào)試環(huán)境下不支持console,執(zhí)行到console.log之類的代碼會(huì)直接報(bào)錯(cuò),只有調(diào)出控制臺(tái)時(shí)才會(huì)生成window.console對象,因此首要引入console-polyfill,具體作用可參見其源代碼。

1.2.1、保留字問題

ECMAScript定義了一套關(guān)鍵字和保留字,根據(jù)規(guī)定,關(guān)鍵字是保留的,不能用作變量名或函數(shù)名。在實(shí)現(xiàn)ECMAScript 3JavaScript引擎中使用關(guān)鍵字作標(biāo)識(shí)符,會(huì)導(dǎo)致"Identifier Expected"(缺少標(biāo)識(shí)符)錯(cuò)誤。而使用保留字作標(biāo)識(shí)符可能會(huì)也可能不會(huì)導(dǎo)致相同的錯(cuò)誤,具體取決于特定的引擎。

break do instanceof typeof case else
new var catch finally return void
continue for switch while debugger function
this with default if throw delete
in try abstract enum int short
boolean export interface static byte extends
long super char final native synchronized
class float package throws implements protected
volatile double import public package let
yield

ECMAScript 5對使用關(guān)鍵字和保留字的規(guī)則進(jìn)行了少許修改。關(guān)鍵字和保留字雖然仍然不能作為標(biāo)識(shí)符使用,但可以用作對象的屬性名,IE8自然是不支持新特性的,會(huì)將屬性名當(dāng)作保留字處理,定位“缺少標(biāo)識(shí)符”錯(cuò)誤時(shí)基本上都會(huì)指向某個(gè)保留字,使用插件es3ify-webpack-plugin可轉(zhuǎn)換對象訪問方式,將點(diǎn)運(yùn)算符訪問改為使用中括號(hào)運(yùn)算符訪問。

// In
var x = {class: 2,};
x.class = [3, 4,];
 
// Out:
var x = {"class": 2};
x["class"] = [3, 4];

混淆插件uglifyjs-webpack-plugin開啟ie8支持能達(dá)到同樣效果,因此可以只保留uglifyjs-webpack-plugin插件。

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

new UglifyJsPlugin({
      uglifyOptions: {
         ie8: true
   } 
})

1.2.2、ECMAScript 5

IE8不支持ECMAScript5,不能使用Array.map、Function.bind等方法,引入es5-shim可擴(kuò)展出大部分ES5 API

唯一重要且不能模擬的是Object.defineProperty,直接通過變量賦值的方式觸發(fā)事件分發(fā),已經(jīng)超出舊引擎的能力范圍了,像Vue.js這些使用到該特性的第三方庫均不可能兼容IE8。

<html>
 <head></head>
 <body> 
  <p id="time"></p>  
  <script>
    // run in internet explorer 9+
    var obj = {};
    var text = "";
    Object.defineProperty(obj, "a", {
      set: function(newValue) {
        text = newValue;
        document.getElementById("time").innerText = text;
      },
      get: function() {
        return text;
      }
    });

    setInterval(function() {
      obj.a = new Date().toString();
    },
    1000);
</script> 
 </body>
</html>

1.2.3、ECMAScript 6

Webpack 2.x / 3.x


基本上引入babel-polyfill足夠了,主要是兼容Promise,引入現(xiàn)版本babel-polyfill已經(jīng)不需要前置引入es5-shim。

  • [必要] devtool設(shè)為source-map,不想生成js.map文件可以設(shè)成false
  • [非必要] 引入es5-shim,不適用復(fù)雜場景,babel-polyfill可以代勞。
  • [非必要] 引入es5-sham,不適用復(fù)雜場景。
  • [非必要] 引入es3ify-webpack-plugin,不使用React情況下,想觀察轉(zhuǎn)碼結(jié)果可以單獨(dú)引入。
  • [必要] 引入uglifyjs-webpack-plugin,并開啟ie8支持。
  • [必要] 引入console-polyfill。
  • [必要] 引入babel-polyfill。

Webpack 4.x


改動(dòng)大致如下:

  • [必要] @babel/polyfill,全盤引入,省時(shí)省力,不要多想。
  • [必要] anujs@1.5.2,額,用1.5.3以上版本可能會(huì)翻車,針對ie8這種老古董,沒事還是不要折騰核心庫。
  • [必要] 不要引入es5-shim,堆棧溢出。
  • [必要] 不要引入es5-sham,堆棧溢出。
  • [必要] 引入插件@babel/plugin-transform-modules-commonjs,能繞過'Accessors not supported!'異常。
{
  "presets": [
    [
      "@babel/env",
      {
        "loose": true
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-transform-modules-commonjs",
    "@babel/plugin-proposal-class-properties"
  ]
}
  • [必要] 不能使用splitChunk分包功能。
  • [必要] 引入uglifyjs-webpack-plugin,見下文。
  • [非必要] 引入@babel/plugin-proposal-class-properties,支持如下寫法,不用到處bind回調(diào)方法。
class Test extends React.Component
{
      selfMethod = () => {
            /// do something
      };

      render()
      {
            return ( <button onClick={this.selfMethod}>Button</button> );
      }
}

二、Webpack配置


2.1、核心插件

Webpack 2.x / 3.x


某些插件在Webpack2.x3.x下具有不同適用性,安裝的時(shí)候注意版本號(hào)。

插件 / 版本 webpack 2.7.0 webpack 3.12.0
react 0.14.9 0.14.9
react-dom 0.14.9 0.14.9
react-router 1.0.3 1.0.3
redux 3.5.2 3.5.2
webpack-dev-server 2.11.2 2.11.2
webpack-dev-middleware 2.0.6 2.0.6
webpack-hot-middleware >=2.22.2 >=2.22.2
url-loader 0.6.2 >=1.0.1
extract-text-webpack-plugin 2.1.2 3.0.2
history 1.7.0 1.7.0
es3ify-webpack-plugin >=0.0.1 >=0.1.0

測不出版本上限的插件全部用>=標(biāo)注,可以嘗試用最新版。
react能兼容ie8的最后版本是0.14.9。
redux版本需要<3.6.0,我不清楚3.5.x3.6.0版本之間發(fā)生了什么。
react-router需要<2.0。
react-redux@4.4.10是最后支持React 0.14的版本。
webpack-dev-middleware<3.0.0。
history用于手動(dòng)路由路轉(zhuǎn),配合react-router。
anujs@1.5.2比較穩(wěn)定。

Webpack 4.x


React全家桶保留Webpack 2.x / 3.x的兼容版本,其他插件基本上使用最新的即可。
因?yàn)獒槍?code>IE8,需要使用uglifyjs-webpack-plugin代替默認(rèn)的terser-webpack-plugin進(jìn)行代碼壓縮。

optimization: {
        minimize: true,
        minimizer:  [
            new UglifyJsPlugin({
                uglifyOptions: {
                    warnings: false,
                    parse: {},
                    compress: false,
                    mangle: true,
                    output: null,
                    toplevel: false,
                    nameCache: null,
                    ie8: true,
                    keep_fnames: false
                }
            })
        ]
    }

因?yàn)?code>IE8不支持WebSocket,IE8預(yù)覽網(wǎng)頁需要devServer使用生產(chǎn)環(huán)境配置實(shí)時(shí)watch編譯,但代碼被壓縮不容易排錯(cuò),因此還需要啟動(dòng)另外一個(gè)devServerChrome下進(jìn)行開發(fā)。

2.2、入口文件順序

entry項(xiàng)定義入口文件,其中包含必需的兼容庫,視情況甚至還需要引入jQuery
main為自定義入口文件,在main.js完成ReactDOM.render等啟動(dòng)操作,視個(gè)人習(xí)慣而言。

entry: {
        "console-polyfill": "console-polyfill",
        "es5-shim": "es5-shim/es5-shim.js",
        "es5-sham": "es5-shim/es5-sham.js",
        "babel-polyfill": "babel-polyfill",
        main: [path.resolve("src", "index.js")]
    }

由于html-webpack-plugin默認(rèn)注入順序是不可預(yù)測的,可能會(huì)出現(xiàn)入口文件引入順序不是兼容庫填充順序的情況出現(xiàn)。

<script type="text/javascript" src="./scripts/babel-polyfill.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/main.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/es5-shim.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/es5-sham.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/console-polyfill.js?fbe5d7c0c4f12cdfda8c"></script>

解決辦法之一是不使用entry配置,手動(dòng)整理出需要加載的靜態(tài)資源,然后用<script>標(biāo)簽在模版文件中依次注入,這樣的話就不需要配置entry了,只留一個(gè)入口文件main.js就夠了。

另外一種方法是指定html-webpack-plugin插件的配置項(xiàng)chunksSortMode"manual",打包時(shí)入口文件會(huì)按配置項(xiàng)chunks指定的順序注入。

plugins: [
        new HtmlWebpackPlugin(
            {
                template: path.join("src", "index.html"),
                favicon: path.join("src", "favicon.ico"),
                minify: false,
                hash: true,
                inject: true,
                chunks: ["console-polyfill", "es5-shim", "es5-sham", "babel-polyfill", "main"],
                chunksSortMode: "manual"
            })
    ]
<script type="text/javascript" src="./scripts/console-polyfill.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/es5-shim.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/es5-sham.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/babel-polyfill.js?fbe5d7c0c4f12cdfda8c"></script>
<script type="text/javascript" src="./scripts/main.js?fbe5d7c0c4f12cdfda8c"></script>

三、CSS模塊化


Webpackloader預(yù)處理類似于管道,上一個(gè)loader的輸出作為下一個(gè)loader的輸入,用數(shù)組表示就是索引大的loader的輸出作為索引小loader的輸入。

{
    test: /\.less$/,
    use: [{
        loader: "style-loader" // creates style nodes from JS strings
    },
    {
        loader: "css-loader" // translates CSS into CommonJS
    },
    {
        loader: "less-loader" // compiles Less to CSS
    }]
}

3.1、預(yù)處理和分離

對于less文件的編譯,less-loaderless文件編譯輸出為css代碼,css代碼中的url會(huì)在css-loader中被轉(zhuǎn)換,樣式代碼隨入口文件一并打包,style-loader在運(yùn)行時(shí)生成<style>標(biāo)簽,并將樣式代碼注入到<style>標(biāo)簽中。

Webpack 2.x / 3.x


為了提高頁面加載效率,需要使用插件extract-text-webpack-plugin將樣式代碼從入口文件中分離存儲(chǔ)為css文件。

const ExtractTextPlugin = require('extract-text-webpack-plugin');
 
// Create multiple instances
const extractCSS = new ExtractTextPlugin('stylesheets/[name]-one.css');
const extractLESS = new ExtractTextPlugin('stylesheets/[name]-two.css');
 
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: extractCSS.extract([ 'css-loader', 'postcss-loader' ])
      },
      {
        test: /\.less$/i,
        use: extractLESS.extract([ 'css-loader', 'less-loader' ])
      },
    ]
  },
  plugins: [
    extractCSS,
    extractLESS
  ]
};

無論用什么預(yù)處理器,style-loadercss-loader總是固定存在的,配置
也是基本相同的,以至于可以編寫一個(gè)模版函數(shù)來統(tǒng)一生成這些預(yù)處理配置。

Webpack 4.x


到了Webpack 4.xextract-text-webpack-plugin已被mini-css-extract-plugin代替。
2.x / 3.x不同,4.x提取css在預(yù)處理中間階段進(jìn)行,因此與style-loader互斥。
因此進(jìn)行css分離時(shí),不需要添加style-loader
如配置開發(fā)環(huán)境時(shí),此時(shí)不需要分離css文件:

{
            test: /\.less$/i,
            use: [ "style-loader","css-loader","less-loader"]
}

到了生產(chǎn)環(huán)境,需要從js文件中分離樣式:

{
        test: /\.less$/i,
        use: [ 
           { 
              loader :require("mini-css-extract-plugin").loader 
           },
           "css-loader",
           "less-loader"
         ]
}

///////// 生產(chǎn)環(huán)境需要在插件項(xiàng)配置 "mini-css-extract-plugin"

plugins:[  
        new MiniCssExtractPlugin({
            filename: outputPublicPath("[hash].css", "stylesheet")
        })
]

相當(dāng)于生產(chǎn)環(huán)境下,mini-css-extract-plugin/loader代替了style-loader。

樣式壓縮,4.x提供了統(tǒng)一的入口插件optimize-css-assets-webpack-plugincss-loader已經(jīng)移除了minimize選項(xiàng)。

const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

....

optimization: {
        minimize: true,
        minimizer: [
            new OptimizeCSSAssetsPlugin()
        ]
    }

3.2、模塊化

Webpack 2.x / 3.x


css-loader自帶模塊化功能,其實(shí)就是混淆,需要指定配置項(xiàng)moduleslocalIdentName才會(huì)生效。

{
    loader: 'css-loader',
    options: {
        modules: true,
        localIdentName: '[path][name]__[local]--[hash:base64:5]'
    }
}

模塊機(jī)制下的css編寫與傳統(tǒng)方式有些許不同,圖片url是相對源代碼目錄的,經(jīng)Webpack編譯處理后才會(huì)轉(zhuǎn)為網(wǎng)絡(luò)資源路徑,但當(dāng)使用到less-loader這類預(yù)處理器時(shí),這個(gè)環(huán)節(jié)會(huì)出錯(cuò),css-loader不能處理上一級(jí)的輸出,例下less文件:

.app
{
  background-image: url("./img/pic.png");

  .red
  {
    color: red;
  }
}

.redColor{
  color: red;
}

當(dāng)啟動(dòng)模塊化功能時(shí),css-loader會(huì)直接提示"Module not found",找不"pic.png",需要在中間加一層resolve-url-loader作轉(zhuǎn)換,完整的配置如下:

{
    test:/\.less$/,
    use:extractLESS.extract({
        fallback:{
            loader:"style-loader",
            options:{
                sourceMap:true
            }
        },
        use:[{
            loader:"css-loader",
            options:{
                sourceMap:true,
                minimize:config.compress,
                localIdentName:"[name]-[local]-[hash:base64:5]",
                modules:true
            }
        },{
            loader:"resolve-url-loader"
        },{
            loader:"less-loader",
            options:{
                sourceMap:true
            }
        }],
        publicPath:"../"
    })
}

Webpack 4.x


4.x開啟混淆只需要設(shè)置modulestrue。

{
        test: /\.less$/i,
        use:[
              {
                  loader: 'css-loader',
                  options: {
                      modules:true
                  }
              },
              "less-loader"
        ]
}

3.3、全局沖突問題

模塊化解決了全局污染問題,但是也可能導(dǎo)致全局樣式失效,只要途徑同一預(yù)處理管道的樣式文件,選擇器名稱無例外都會(huì)被混淆,導(dǎo)致與容器的class匹配不上,因此需要把全局作用的樣式文件從模塊化管道中排除掉,最簡單的方法是分開目錄存放,然后修改配置項(xiàng)的正則表達(dá)式,通過目錄名稱來過濾。

/* In */
.app
{
  background-image: url("./img/pic.png");

  .red
  {
    color: red;
  }
}


/* Out */
.src-components-app-styles---app---1-oEM
{
   background-image: url("./images/pic.png");
}

.src-components-app-styles---app---1-oEM .src-components-app-styles---red---12lu-
{
  color: red;
}
 import React from 'react';
 import './app.less';
 
 export default () => {
   return (
      // app -> src-components-app-styles---app---1-oEM
     <div className="app">
       <p className="red">Hello World</p>
     </div>
   );
 };

3.3.1、方案一

CSS Modules允許使用:global(.className)的語法,聲明一個(gè)全局規(guī)則。凡是這樣聲明的class,都不會(huì)被編譯成哈希字符串。

.title {
   color: red;
 }
 
 :global(.title) {
   color: green;
 }

CSS Modules還提供一種顯式的局部作用域語法:local(.className),等同于.className,所以上面的css也可以寫成下面這樣。

:local(.title) {
   color: red;
 }
 
 :global(.title) {
   color: green;
 }

3.3.2、方案二

通過文件后綴來區(qū)分是否是全局作用的樣式文件,擴(kuò)展名為“.less”則視為全局作用,后綴為“.scope.less”則視為局部作用,通過區(qū)分后綴是否包含scope字眼來分開編譯,但正則表達(dá)式不擅長“不包含”的識(shí)別,不要企圖通過/(scope){0}.less/來區(qū)分,你會(huì)發(fā)現(xiàn)路徑?jīng)_突根本不會(huì)通過編譯。
預(yù)處理配置項(xiàng)test除了可以接受正則表達(dá)式外,使用回調(diào)函數(shù)也是可以的,指定一個(gè)傳入?yún)?shù)為文件物理路徑的function,通過返回布爾值來代替正則表達(dá)式的test操作:

// custom function
const lang= "less"; // lang = "less" 、"sass" .....
const suffix = `.scope.${lang}`; 
const ext = `.${lang}`;

// if path include 'scope.less'
function test(path) {
    return path.lastIndexOf(ext) == path.length - ext.length && path.lastIndexOf(suffix) == path.length - suffix.length;
}

// webpack config
{
    test:test,
    use:extractLESS.extract({
        fallback:{
            loader:"style-loader",
            options:{
                sourceMap:true
            }
        },
        use:[{
            loader:"css-loader",
            options:{
                sourceMap:true,
                minimize:config.compress,
                localIdentName:"[name]-[local]-[hash:base64:5]",
                modules:true
            }
        },{
            loader:"resolve-url-loader"
        },{
            loader:"less-loader",
            options:{
                sourceMap:true
            }
        }],
        publicPath:"../"
    })
}

或者使用include/exclude參數(shù),例如create-react-app腳手架默認(rèn)文件名后綴為module.less為模塊化樣式文件,那么全局樣式通道可設(shè)置exclude排除模塊化樣式文件,實(shí)現(xiàn)分流。

// 模塊化
{
     test: /\.module\.(less)$/i,
     use: [
          {
                loader:"css-loader",
                options:{ modules:true }
          },
          "less-loader"  
    ]
}

// 全局作用
{
     test: /\.(less)$/i,
     exclude: /\.module\.(less)$/i,
     use: [
          {
                loader:"css-loader"
          },
          "less-loader"  
    ]
}

3.3、classnames插件

JSX書寫className十分繁瑣,可以使用插件classnames簡化操作,具體使用方法參看官方文檔。
https://github.com/JedWatson/classnames
https://www.npmjs.com/package/classnames

npm:
npm install classnames --save

Bower:
bower install classnames --save

Yarn (note that yarn add automatically saves the package to the dependencies in package.json):
yarn add classnames

模塊機(jī)制下,樣式文件導(dǎo)入后實(shí)際為一個(gè)map

{
    app: "src-components-app-styles---app---1-oEM",
    red: "src-components-app-styles---red---12lu-",
    big: "src-components-app-styles---big---Y9ObK"
}

JSXclassName只接受字符串,需自行拼接各個(gè)鍵值后賦值給className

import styles from "app.less"

<div className={styles.app+ " " +styles.red}>
  <p className={styles.red}></p>
</div>

// or

<div className={String.join(styles.app,styles.red," ")}>
  <p className={styles.red}></p>
</div>

使用classnames包裝,實(shí)質(zhì)仍然是輸出字符串,但不用寫String.join或加號(hào)了:

import styles from "app.less"
import classNames from "classnames";

<div className={classNames(styles.app,styles.red)}>
  <p className={styles.red}></p>
</div>

不想書寫“styles.”,可以再進(jìn)一步包裝:

import styles from "app.less"
import classNames from "classnames/bind";

var cx = classNames.bind(styles);

<div className={cx('app','red')}>
  <p className={styles.red}></p>
</div>

理解其原理后,甚至可以:

function bindStyles(styles)
{
    return function(...argv){
        let _cx=classNames.bind(styles);
        let keys=argv.filter((i)=>typeof i =="string");
        console.log(keys)
        let maps=argv.filter((i)=>typeof i =="object");
        return [
            Object.entries(styles).filter((i)=>keys.indexOf(i[0])>=0).map((i)=>i[1]).join(" "),
            _cx(Object.assign({},...maps))
        ].join(" ");
    };
}

//////
import styles from "app.less"
import classNames from "classnames/bind";

let cx=bindStyles(styles);

<div className={cx("app",{"red":true})}>
  <p className={styles.red}></p>
</div>

四、React-Like


React 0.14過于老舊,部分特性已被警告使用。部分第三方React-Like框架可以兼容IE8,并且支持React15/16的特性,但redux/react-router還是要使用兼容版本。

anujs

https://github.com/RubyLouvre/anu

//webpack配置
resolve: {
   alias: {
      'react': 'anujs',
      'react-dom': 'anujs',
        // 若要兼容 IE 請使用以下配置
        // 'react': 'anujs/dist/ReactIE',
        // 'react-dom': 'anujs/dist/ReactIE',
        // 'redux': 'anujs/lib/ReduxIE',//這主要用于IE6-8,因?yàn)楣俜皆创a中的isPlainObject方法性能超差
        // 如果引用了 prop-types 或 create-react-class
        // 需要添加如下別名
        'prop-types': 'anujs/lib/ReactPropTypes',
        'create-react-class': 'anujs/lib/createClass'
        //如果你在移動(dòng)端用到了onTouchTap事件
        'react-tap-event-plugin': 'anujs/lib/injectTapEventPlugin',  
   }
}

使用官方配置進(jìn)行替換,即可順利編譯和運(yùn)行,無須額外依賴。

nervjs

京東的輪子

https://github.com/NervJS/nerv
https://www.npmjs.com/package/nervjs
https://nervjs.github.io/docs/

使用時(shí)需要對引用作一些替換:

/* 
import React from "react"
import ReactDom from "react-dom"

replace with

import Nerv from "nervjs"
*/
import Nerv from "nervjs"

class App extends Nerv.Component {
  render() {
    return <TodoBox />;
  }
}

具體配置參考官方的IE8模版:
https://github.com/NervJS/nerv-webpack-boilerplate
IE8環(huán)境下nervjs自身可以正常運(yùn)行,但不能整合react-router@1.0.3。

五、其它坑


  • IE8不支持Websocket,只能使用現(xiàn)代瀏覽器進(jìn)行日常開發(fā),編譯后再觀察IE8運(yùn)行效果。
  • React編譯樣式比較慢,尤其通過import方式引用樣式時(shí),dev-server經(jīng)常因爆內(nèi)存而崩潰。
  • 沒有支持IE8的完善組件庫,極有可能仍然離不開JQuery。
    https://www.npmjs.com/package/jquery
    封裝JQuery組件時(shí)盡量在componentWillUnmount中釋放資源,但并不是所有第三方控件都有資源釋放接口的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容