Как построить фреймворк REACT для всей семьи

React.js

Технология фронтенда развивается слишком быстро, и версии некоторых библиотек постоянно обновляются.Сейчас онлайн туториалы по сборке либо неполные, либо версия слишком низкая.Я интегрировал некоторые туториалы и собственное понимание, и разобрался в них, чтобы каждый мог быстро запустить React Framework. Этот учебник предназначен для начинающих и тех, кто перешел на технологический стек. Примечание. (Это руководство было написано 29 марта 2019 г., обратите внимание на версию)! ! !

При прочтении, если обнаружите какие-либо проблемы, можете их поднять, а я их со временем обновлю (мне лень, и некоторые команды не набраны, пожалуйста, читайте их внимательно, чтобы не было пропусков!!!)

предисловие

Я также являюсь членом армии реагирования на полпути.Из-за присоединения на полпути я мало понимаю общую структуру.У меня всегда было ограниченное понимание использования готовой среды DVA. Я обычно сталкиваюсь с проблемами, и всегда нужно найти информацию для их решения.Есть также проблемы, которые трудно решить в конце концов.Для того, чтобы облегчить мое понимание всей технологии, связанной с реакцией, и чтобы такие люди, как я, не продолжали чтобы ступить на яму, я могу получить более подробное руководство, основанное на этом руководстве. (Мастера не стрелять)! ! !

Описание Проекта

1. Стек технологий в настоящее время актуален

  • node 8.11.1
  • react 16.8.6
  • react-router-dom 5.0.0
  • redux 4.0.1
  • webpack 4.28.2

2. Инструменты управления пакетами

Обычно используются npm yarn и т. д. Здесь я использую пряжу, и друзья, которые используют npm, обращают внимание на разницу между следующими командами.

начать прямо

Инициализировать проект

  1. Сначала создайте каталог и введите
mkdir react-cli && cd react-cli
  1. Инициализируйте проект, заполните информацию о проекте (можно нажать Enter до конца)
npm init

Установить веб-пакет

yarn global add webpack -D 
yarn global add webpack-cli -D 
  • пряжа использует add для добавления пакетов, -D равно --save-dev -S равно --save
  • Разница между -D и -S: -D — это то, от чего вы зависите при разработке, --S — это то, от чего вы все еще зависите после публикации.
  • -g — это глобальная установка, что нам удобно использовать позже командой webpack (для тех, кто все еще не может использовать ее после глобальной установки, проверьте собственную переменную окружения PATH)

После установки создайте новый каталог сборки и поместите конфигурацию разработки на основе веб-пакета webpack.dev.config.js

mkdir build && cd build && echo. > webpack.dev.config.js

Содержание конфигурации очень простое, настройте вход и выход

const path = require('path');

module.exports = {
 
    /*入口*/
    entry: path.join(__dirname, '../src/index.js'),
    
    /*输出到dist目录,输出文件名字为bundle.js*/
    output: {
        path: path.join(__dirname, '../dist'),
        filename: 'bundle.js'
    }
};

Затем создайте файл ../src/index.js в соответствии с адресом файла записи, который мы настроили (обратите внимание, что каталог src находится на том же уровне, что и каталог сборки)

mkdir src && cd src && echo. > index.js

затем напишите строку

document.getElementById('app').innerHTML = "Hello React";

Теперь выполните команду упаковки webpack в корневом каталоге.

webpack --config ./build/webpack.dev.config.js

Мы видим, что директория dist и bundle.js сгенерированы. (Чтобы устранить предупреждение, см. конфигурацию режима позже) Затем мы создаем новый index.html в каталоге dist для ссылки на этот упакованный файл.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
</body>
</html>

Затем дважды щелкните, чтобы открыть index.html, и мы увидим вывод браузера.

Hello React

Таким образом, наша основная функция упаковки выполнена! ! !

mode

Упаковка прошла успешно только сейчас, но с предупреждением, а это значит, что webpack4 требует от нас указать тип режима, чтобы отличить среду разработки от производственной среды. Он поможет нам автоматически выполнять соответствующие функции. Режим можно написать на команда запуска --mode=production или development , также может быть записана в файл конфигурации, здесь мы Добавьте атрибут режима в файл webpack.dev.config.js.

/*入口*/
    entry: path.join(__dirname, '../src/index.js'),
    mode:'development',

После выполнения команды pack предупреждение исчезло.

babel

Babel 把用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本。 
这一过程叫做“源码到源码”编译, 也被称为转换编译。(本教程使用的babel版本是7,请注意包名和配置与6的不同)
  • @babel/core вызывает API Babel для транскодирования
  • @babel/preset-env для разбора ES6
  • @babel/preset-react для парсинга JSX
  • вавилон-загрузчик загрузчик
yarn add @babel/core @babel/preset-env @babel/preset-react babel-loader -D

Затем создайте новый файл конфигурации babel в корневом каталоге.

babel.config.js

const babelConfig = {
   presets: ["@babel/preset-react", "@babel/preset-env"],
    plugins: []
}

module.exports = babelConfig;

Измените webpack.dev.config.js и добавьте babel-loader!

/*src目录下面的以.js结尾的文件,要使用babel解析*/
/*cacheDirectory是用来缓存编译结果,下次编译加速*/
module: {
    rules: [{
        test: /\.js$/,
        use: ['babel-loader?cacheDirectory=true'],
        include: path.join(__dirname, '../src')
    }]
}

Теперь давайте просто проверим, можно ли правильно экранировать ES6~

Изменить src/index.js

 /*使用es6的箭头函数*/
    var func = str => {
        document.getElementById('app').innerHTML = str;
    };
    func('我现在在使用Babel!');

Выполните команду пакета еще раз

webpack --config ./build/webpack.dev.config.js

Теперь обновите index.html в разделе dist, и вы увидите вывод браузера.

我现在在使用Babel!

Если вам интересно, вы можете открыть упакованный bundle.js, Внизу вы обнаружите, что функция стрелки ES6 была преобразована в обычную функцию function.

react

Далее наш ключевой контент, доступ к реакции

yarn add react react-dom -S

Примечание. -S здесь используется для обеспечения зависимостей производственной среды.

Измените src/index.js, чтобы использовать реакцию

import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(
    <div>Hello React!</div>, document.getElementById('app'));

Выполните команду пакета

webpack --config ./build/webpack.dev.config.js

Обновите index.html, чтобы увидеть эффект.

Затем мы используем идею компонентизации реакции, чтобы сделать некоторую инкапсуляцию, создать каталог компонентов в src, а затем создать новый каталог Hello, создать в нем index.js и написать:

import React, { PureComponent } from 'react';

export default class Hello extends PureComponent  {
    render() {
        return (
            <div>
                Hello,组件化-React!
            </div>
        )
    }
}

Затем давайте изменим src/index.js, чтобы он ссылался на компонент Hello!

import React from 'react';
import ReactDom from 'react-dom';
import Hello from './components/Hello';

ReactDom.render(
    <Hello/>, document.getElementById('app'));

Примечание: импорт модульного импорта по умолчанию выберет индексный файл в каталоге, поэтому напишите его прямо как «./components/Hello».

Выполните команду пакета в корневом каталоге

webpack --config ./build/webpack.dev.config.js

Откройте index.html, чтобы увидеть эффект~

Оптимизация команд

Вводить длинную команду упаковки каждый раз при упаковке очень хлопотно, давайте оптимизируем это.

Измените объект сценария в package.json, добавьте атрибут сборки и напишите нашу команду упаковки.

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config ./build/webpack.dev.config.js"
  },

Теперь нам нужно только выполнить npm run build, чтобы упаковать его! (За исключением того, что start является встроенной командой, другие новые команды необходимо запускать с помощью команды run)

react-router

Теперь мы получаем доступ к реактивному маршруту

yarn add react-router-dom -S

Затем, чтобы использовать маршрутизацию, мы создаем две страницы для переключения содержимого маршрутизации. Сначала создайте каталог страниц в src, затем создайте домашние каталоги и каталоги страниц соответственно в каталоге страниц и создайте в нем index.js.

src/pages/home/index.js
import React, {PureComponent} from 'react';

export default class Home extends PureComponent {
    render() {
        return (
            <div>
                this is home~
            </div>
        )
    }
}
src/pages/page/index.js
import React, {PureComponent} from 'react';

export default class Page extends PureComponent {
    render() {
        return (
            <div>
                this is Page~
            </div>
        )
    }
}

Две страницы написаны, а затем создайте наш компонент навигации по меню.

components/Nav/index.js
import React from 'react';
import { Link } from 'react-router-dom';

export default () => {
    return (
        <div>
            <ul>
                <li><Link to="/">首页</Link></li>
                <li><Link to="/page">Page</Link></li>
            </ul>
        </div>
    )
}

Примечание. Используйте компонент «Ссылка», чтобы изменить текущий маршрут.

Затем мы создаем новый router.js в src, пишем наши маршруты и связываем их со страницей.

import React from 'react';

import { Route, Switch } from 'react-router-dom';

// 引入页面
import Home from './pages/home';
import Page from './pages/page';

// 路由
const getRouter = () => (
    <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/page" component={Page}/>
    </Switch>
);

export default getRouter;

Страницы, меню и маршруты написаны, и мы связываем их. в src/index.js

import React from 'react';
import ReactDom from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom';
import Nav from './components/Nav';
import getRouter from './router';

ReactDom.render(
    <Router>
        <Nav/>
        {getRouter()}
    </Router>,
    document.getElementById('app')
)

Теперь вы можете видеть содержимое после выполнения сборки сборки npm run, но нажатие на меню не отвечает, что нормально. Поскольку мы по-прежнему используем путь к локальному диску, а не форму ip + порт, затем мы вводим webpack-dev-server для запуска простого сервера.

yarn global add webpack-dev-server -D

Измените webpack.dev.config.js и добавьте конфигурацию webpack-dev-server.

// webpack-dev-server
devServer: {
    contentBase: path.join(__dirname, '../dist'), 
    compress: true,  // gzip压缩
    host: '0.0.0.0', // 允许ip访问
    hot:true, // 热更新
    historyApiFallback:true, // 解决启动后刷新404
    port: 8000 // 端口
},

Примечание: contentBase вообще не подходит, в основном для разрешения доступа к файлам в указанном каталоге, здесь используется index.html под dist

Затем создайте новую команду запуска в package.json.

"start": "webpack-dev-server --config ./build/webpack.dev.config.js",

Открыть после выполнения команды запуска npmhttp://localhost:8000Вы можете видеть содержимое и переключать маршруты!

прокси

В разделе devServer есть атрибут прокси для установки нашего прокси.

 devServer: {
       ...
        proxy: { // 配置服务代理
            '/api': {
                 target: 'http://localhost:8000',
                 pathRewrite: {'^/api' : ''},  //可转换
                 changeOrigin:true
            }
        },
        port: 8000 // 端口
    },

Если у вас есть серверная служба на локальном хосте: 8000, вы можете включить прокси следующим образом. Запросы к /api/users теперь будут проксироваться в запросыhttp://локальный:8000/пользователи. (обратите внимание на второй атрибут здесь, он заменяет '/api' на''). changeOrigin: true может помочь нам решить междоменные проблемы.

оптимизация инструмента разработки

Когда при запуске сообщается об ошибке или о точке останова, вы обнаружите, что упакованный код не может быть запущен. Добавляем в вебпак

devtool: 'inline-source-map'

Затем вы можете увидеть код, который мы написали в srouce, и вы также можете отладить точку останова~

оптимизация пути к файлу

Обычно, когда мы обращаемся к компоненту или странице, он обычно используется в форме ../. Если уровень файла слишком глубокий, это приведет к ситуации ../../../, которую нелегко поддерживать и понимать, поэтому webpack предоставляет настройку псевдонимов.

См. здесь: Помните, что имя не может быть объявлено как имя другого пакета, который вы импортируете. Псевдонимы перезапишут имя вашего пакета, не позволяя вам ссылаться на другие пакеты. Каштаны: редукс, реакция и т. д.

Сначала добавьте в webpack.dev.config.js

resolve: {
    alias: {
        pages: path.join(__dirname, '../src/pages'),
        components: path.join(__dirname, '../src/components'),
        router: path.join(__dirname, '../src/router')
    }
}

Затем мы можем импортировать компоненты в router.js и изменить на

// 引入页面
import Home from './pages/home';
import Page from './pages/page';

// 引入页面
import Home from 'pages/home';
import Page from 'pages/page';

Чем сложнее этот функциональный уровень, тем лучше.

redux

Далее нам нужно интегрировать редукс.Давайте не будем сначала говорить о теории, а используем редукс как самый распространенный пример, счетчик. Во-первых, мы создаем каталог redux в src и создаем в нем два каталога, действия и редукторы, для хранения наших действий и редукторов соответственно.

首先引入redux
yarn add redux -S

counter.js под действиями в каталоге

/*action*/

export const INCREMENT = "counter/INCREMENT";
export const DECREMENT = "counter/DECREMENT";
export const RESET = "counter/RESET";

export function increment() {
    return {type: INCREMENT}
}

export function decrement() {
    return {type: DECREMENT}
}

export function reset() {
    return {type: RESET}
}

Counter.js под редукторами в каталоге

import {INCREMENT, DECREMENT, RESET} from '../actions/counter';

/*
* 初始化state
 */

const initState = {
    count: 0
};
/*
* reducer
 */
export default function reducer(state = initState, action) {
    switch (action.type) {
        case INCREMENT:
            return {
                count: state.count + 1
            };
        case DECREMENT:
            return {
                count: state.count - 1
            };
        case RESET:
            return {count: 0};
        default:
            return state
    }
}

Добавьте псевдонимы действий и редукторов в конфигурацию веб-пакета.

actions: path.join(__dirname, '../src/redux/actions'),
reducers: path.join(__dirname, '../src/redux/reducers')

Позвольте мне рассказать об этом здесь, функция создания действия в основном возвращает класс действия, а у класса действия есть атрибут типа, чтобы решить, какой редюсер выполнить. Редьюсер — это чистая функция (только принимает и возвращает параметры, не вводит другие переменные и не выполняет другие функции), в основном принимает старое состояние и действие, оценивает выполнение по типу действия, а затем возвращает новое состояние.

特殊说明:你可能有很多reducer,type一定要是全局唯一的,一般通过prefix来修饰实现。栗子:counter/INCREMENT里的counter就是他所有type的前缀。

Далее я либо создам store.js в каталоге redux.

import {createStore} from 'redux';
import counter  from 'reducers/counter';

let store = createStore(counter);

export default store;

Представлены специфические функции магазина:

  • поддерживать состояние приложения;
  • Предоставьте метод getState() для получения состояния;
  • Обеспечьте отправку (действие), чтобы вызвать метод редукторов для обновления состояния;
  • Зарегистрируйте слушателей через subscribe(listener);
  • Отмените регистрацию прослушивателя с помощью функции, возвращаемой subscribe(listener) .

Затем мы создаем страницу счетчика для использования данных избыточности. Создайте каталог counter и index.js в каталоге pages. Страница ссылается на наши действия по выполнению редьюсера для изменения данных.

import React, {PureComponent} from 'react';
import { connect } from 'react-redux';
import { increment, decrement, reset } from 'actions/counter';

class Counter extends PureComponent {
    render() {
        return (
            <div>
                <div>当前计数为{this.props.count}</div>
                <button onClick={() => this.props.increment()}>自增
                </button>
                <button onClick={() => this.props.decrement()}>自减
                </button>
                <button onClick={() => this.props.reset()}>重置
                </button>
            </div>
        )
    }
}
export default connect((state) => state, dispatch => ({
    increment: () => {
        dispatch(increment())
    },
    decrement: () => {
        dispatch(decrement())
    },
    reset: () => {
        dispatch(reset())
    }
}))(Counter);

что такое подключить? react-redux предоставляет метод connect. connect имеет два основных параметра, один — mapStateToProps, который преобразует состояние редукса в реквизиты компонента, а другой параметр — mapDispatchToprops, который преобразует метод запуска действий в функцию свойства props.

Затем мы вводим react-redux:

yarn add react-redux  -S

Затем мы добавляем меню счетчика и маршрутизацию, чтобы продемонстрировать функциональность нашего счетчика.

Nav组件

<li><Link to="/counter">Counter</Link></li>
router.js
import Counter from 'pages/counter';
---
<Route path="/counter" component={Counter}/>

Наконец, используйте функцию хранения в src/index.js.

import {Provider} from 'react-redux';
import store from './redux/store';

ReactDom.render(
    <Provider store={store}>
        <Router>
            <Nav/>
            {getRouter()}
        </Router>
    </Provider>,
    document.getElementById('app')
)

Компонент Provider позволяет всем компонентам получить доступ к хранилищу. Нет необходимости загружать вручную. Нет необходимости контролировать вручную. Затем мы запускаем его, запускаем npm, и тогда мы можем видеть нашу функцию счетчика в браузере.

В нашей разработке будет много редьюсеров, Redux предоставляет функцию combReducers для объединения редюсеров, которая очень проста в использовании. Внедрите combReducers в store.js и используйте его.

import {combineReducers} from "redux";

let store = createStore(combineReducers({counter}));

Затем мы можем изменить состояние, введенное подключением к счетчику в компоненте страницы счетчика (выберите нужный набор данных в полном дереве состояний).

export default connect(({counter}) => counter, dispatch => ({
    increment: () => {
        dispatch(increment())
    },
    decrement: () => {
        dispatch(decrement())
    },
    reset: () => {
        dispatch(reset())
    }
}))(Counter);

Чтобы разобраться с рабочим процессом редукции:

  1. Вызовите store.dispatch(action) для отправки действия.
  2. Хранилище избыточности вызывает входящую функцию редуктора. Передача в текущем состоянии и действии.
  3. Корневой редуктор должен объединять несколько выходных данных дочернего редуктора в единое дерево состояний.
  4. Хранилище Redux содержит полное дерево состояний, возвращаемое корневым редьюсером.

Оптимизация HtmlWebpackPlugin

Прежде чем мы прошли через веб-пакет внутри

contentBase: path.join(__dirname, '../dist'),

Настройте, чтобы получить доступ к dist/index.html. Более хлопотно написать импортированный JS до смерти. Этот плагин будет каждый раз автоматически вставлять js в ваш шаблон index.html.

yarn add html-webpack-plugin -D

Затем прокомментируйте конфигурацию contentBase веб-пакета, создайте новый общедоступный каталог в корневом каталоге, переместите index.html из dist в общедоступный и удалите ссылку на bundle.js.

Затем добавьте конфигурацию html-webpack-plugin в webpack.dev.config.js.

const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
    new HtmlWebpackPlugin({
        filename: 'index.html',
        template: path.join(__dirname, '../public/index.html')
    })
]

Далее мы будем использовать этот html-webpack-plugin каждый раз при запуске, и webpack будет автоматически внедрять упакованный JS в этот шаблон index.html.

Скомпилировать оптимизацию css

Сначала введите загрузчик css

yarn add css-loader style-loader -D

Затем добавьте файл index.css в наш предыдущий каталог pages/page и напишите строку css

.page-box {
    border: 1px solid red;
    display: flex;
}

Затем мы вводим и используем в page/index.js

import './index.css';

<div class="page-box">
    this is Page~
</div>

Наконец, мы позволяем веб-пакету поддерживать загрузку css и добавляем его в правила webpack.dev.config.js.

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

После запуска npm start вы можете увидеть, что стиль вступает в силу, просмотрев маршрут страницы.

  • css-loader позволяет реализовать require() с помощью таких методов, как @import и url(...);

  • Загрузчик стилей добавляет на страницу все вычисленные стили; их комбинация позволяет встраивать таблицы стилей в JS-файлы, входящие в комплект webpack.

Интегрированная оптимизация PostCSS

В предыдущем стиле мы добавили стиль display:flex;, и часто нам нужно добавить префикс браузера при написании CSS. Но добавление его вручную слишком громоздко, PostCSS предоставляет плагин Autoprefixer, чтобы помочь нам в этом.

首先引入相关包

yarn add postcss-loader postcss-cssnext -D

postcss-cssnext позволяет вам использовать будущие функции CSS (включая автопрефиксер).

Затем настройте webpack.dev.config.js

rules: [{
    test: /\.(css)$/,
    use: ["style-loader", "css-loader", "postcss-loader"]
}]

Затем создайте новый postcss.config.js в корневом каталоге.

module.exports = {
    plugins: {
        'postcss-cssnext': {}
    }
};

Теперь вы запускаете код, затем пишете css, переходите в браузер, чтобы проверить элемент и посмотреть, создает ли атрибут префикс браузера!. следующим образом:

编译前
.page-box {
    border: 1px solid red;
    display: flex;
}

编译后
.page-box {
    border: 1px solid red;
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
}

Оптимизация модулей CSS

Правила CSS являются глобальными, а правила стиля любого компонента действительны для всей страницы. Единственный способ создать локальную область — использовать уникальное имя класса, которое не совпадает с именем других селекторов. Это то, что делают модули CSS.

Включаем модули в webpack.dev.config.js

use: ['style-loader', 'css-loader?modules', 'postcss-loader']

Затем, когда мы вводим css, мы можем использовать форму object.property. (Здесь есть символы подчеркивания, способ использования [имя атрибута])

import style from './index.css';

<div className={style["page-box"]}>
    this is Page~
</div>

В это время откройте консоль, и вы обнаружите, что className превратилось в хеш-строку. Затем мы можем его украсить.Используя cssmodules, мы также можем видеть, какой стиль был изначально. Изменить css-загрузчик

之前
css-loader?modules

之后
{
    loader:'css-loader',
    options: {
        modules: true,
        localIdentName: '[local]--[hash:base64:5]'
    }
}

После перезапуска вебпака откройте консоль и обнаружите, что стиль класса стал class="page-box--1wbxe", это очень полезно?

Скомпилировать оптимизацию изображения

Сначала представьте загрузчик изображений

yarn add url-loader file-loader -D

Затем создайте новый каталог изображений в src и поместите изображение a.jpg.

Затем настройте его в правилах webpack.dev.config.js и добавьте псевдоним изображений.

{
    test: /\.(png|jpg|gif)$/,
    use: [{
        loader: 'url-loader',
        options: {
            limit: 8192
        }
    }]
}

images: path.join(__dirname, '../src/images'),

Ограничение параметров 8192 означает, что изображения меньше или равные 8K будут преобразованы в кодировку base64 и вставлены непосредственно в HTML, чтобы уменьшить HTTP-запросы.

Затем мы продолжим на странице только что, импортируем изображение и используем его.

import pic from 'images/a.jpg'

<div className={style["page-box"]}>
    this is Page~
    <img src={pic}/>
</div>

После перезапуска вебпака смотрите изображение.

нагрузка по требованию

Теперь мы видим, что он загружает файл bundle.js при каждом запуске. Когда мы загружаемся выше сгиба, это будет очень медленно. Поскольку он также загружает другие вещи, нам нужна вещь, чтобы различать, что нам нужно загрузить. В настоящее время его условно делят на маршрутный и компонентный. Здесь мы используем общую нагрузку по маршруту. React-loadable предоставляется выше react-router4.0.

首先引入react-loadable

yarn add react-loadable -D

Затем перепишите наш router.js

之前
import Home from 'pages/home';
import Page from 'pages/page';
import Counter from 'pages/counter';

之后
import loadable from 'react-loadable';
import Loading from 'components/Loading';

const Home = loadable({
    loader: () => import('pages/Home'),
    loading: Loading,
    timeout: 10000, // 10 seconds
})
const Page = loadable({
    loader: () => import('pages/page'),
    loading: Loading,
    timeout: 10000, // 10 seconds
})
const Counter = loadable({
    loader: () => import('pages/Counter'),
    loading: Loading,
    timeout: 10000, // 10 seconds
})

loadable нуждается в компоненте загрузки, мы добавляем компонент загрузки в компоненты

import React from 'react';

export default () => {
    return <div>Loading...</div>
};

В это время запуск обнаружит, что ошибка не поддерживает динамический импорт, поэтому нам нужен Babel для поддержки динамического импорта. сначала импортировать

yarn add @babel/plugin-syntax-dynamic-import -D

Затем настройте файл babel.config.js.

plugins: ["@babel/plugin-syntax-dynamic-import"]

Когда вы запустите его снова, вы обнаружите, что под исходником находится не только файл bundle.js. И каждый раз, когда щелкают меню маршрутизации, файл меню будет загружаться заново, который действительно загружается по требованию.

Добавить маршрут 404

Создайте новый ненайденный каталог и компонент страницы 404 в каталоге страниц.

import React, {PureComponent} from 'react';

class NotFound extends PureComponent {
    render() {
        return (
            <div>
                404
            </div>
        )
    }
}
export default NotFound;

Добавьте маршрут 404 в router.js

const NotFound = loadable({
    loader: () => import('pages/notfound'),
    loading: Loading,
    timeout: 10000, // 10 seconds
})

<Switch>
    <Route exact path="/" component={Home}/>
    <Route path="/page" component={Page}/>
    <Route path="/counter" component={Counter}/>
    <Route component={NotFound}/>
</Switch>

В это время, если вы введете несуществующий маршрут, вы обнаружите, что компонент страницы отображается как 404.

Извлечь общедоступный код

Файлы, которые мы запаковали, содержат код типа react, redux, react-router и т. д., и их приходится перезагружать каждый раз при выпуске, на самом деле в этом нет необходимости, мы можем извлечь их отдельно. Настройте запись в webpack.dev.config.js:

entry: {
    app:[
        path.join(__dirname, '../src/index.js')
    ],
    vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
},
output: {
    path: path.join(__dirname, '../dist'),
    filename: '[name].[hash].js',
    chunkFilename: '[name].[chunkhash].js'
},

извлечь css-файл

Мы видим, что под исходниками только файлы js, но на самом деле у нас есть файл css, который запакован в файл js, и теперь мы его извлекаем. Используйте плагин mini-css-extract-plugin для webpack.

yarn add mini-css-extract-plugin -D

Затем настройте в веб-пакете

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

{
    test: /\.css$/,
    use: [{loader: MiniCssExtractPlugin.loader}, {
        loader:'css-loader',
        options: {
            modules: true,
            localIdentName: '[local]--[hash:base64:5]'
        }
    }, 'postcss-loader']
 }
 
 new MiniCssExtractPlugin({ // 压缩css
    filename: "[name].[contenthash].css",
    chunkFilename: "[id].[contenthash].css"
})

Затем после перезапуска вы обнаружите, что в исходниках есть дополнительный файл css, что доказывает, что мы успешно извлекли

тайник

Только что мы написали hash, chunkhash и contenthash при выводе, так для чего они используются?

  • Хэш связан с созданием всего проекта. Пока в проекте есть изменения файлов, значение хеш-функции всей конструкции проекта будет меняться, и все файлы имеют одно и то же значение хеш-функции.
  • Chunkhash отличается от хэша тем, что анализирует зависимые файлы в соответствии с разными входными файлами (Entry), строит соответствующие чанки и генерирует соответствующие хэш-значения.
  • contenthash для уровня содержимого файла.Только при изменении содержимого вашего собственного модуля значение хеш-функции меняется, поэтому мы можем решить проблему апелляции через contenthash

Сборка производственной среды

Цели сборки для разработки и производства сильно различаются. В среде разработки нам нужны исходные карты и локальный сервер с возможностью оперативной перезагрузки или горячей замены модулей. В производственной среде наши цели сместились, чтобы сосредоточиться на меньших пакетах, более легких исходных картах и ​​более оптимизированных ресурсах для сокращения времени загрузки.

Создайте новый файл webpack.prod.config.js в каталоге сборки, скопируйте исходную конфигурацию для модификации. Сначала удалите MiniCssExtractPlugin в webpack.dev.config.js, затем удалите devServer в webpack.prod.config.js, а затем измените команду упаковки.

"build": "webpack --config ./build/webpack.prod.config.js"

Затем измените значение devtool на none.

devtool: 'none',

Далее мы сделаем еще несколько оптимизаций для упаковки.

сжатие файлов

В прошлом webpack использовал uglifyjs-webpack-plugin для сжатия файлов, что уменьшало размер упакованных файлов.

Теперь вам нужно только настроить режим для автоматического использования некоторых конфигураций среды разработки, включая сжатие JS и т. д.

mode:'production',

После упаковки объем значительно уменьшается.

Извлечение общего блока

Это указывает, какие блоки будут выбраны для оптимизации. Когда предоставляется строка, допустимыми значениями являются все, асинхронные и начальные. Предоставление всего может быть особенно эффективным, потому что это означает, что блоки могут использоваться совместно даже между асинхронными и неасинхронными блоками.

optimization: {
    splitChunks: {
      chunks: 'all'
    }
}

После переупаковки вы обнаружите, что размер упаковки стал меньше.

css-сжатие

Мы обнаружили, что после использования конфигурации режима производственной среды JS сжимается, а CSS не сжимается. Здесь мы используем плагин optimise-css-assets-webpack-plugin для сжатия css. Ниже приведены рекомендации официального сайта.

虽然webpack 5可能内置了CSS minimizer,但是你需要携带自己的webpack 4。要缩小输出,请使用像optimize-css-assets-webpack-plugin这样的插件。设置optimization.minimizer会覆盖webpack提供的默认值,因此请务必同时指定JS minimalizer:

сначала импортировать

yarn add optimize-css-assets-webpack-plugin -D

Добавьте конфигурацию упаковки webpack.prod.config.js

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

plugins: [
    ...
    new OptimizeCssAssetsPlugin()
],

Переупакуйте, и вы обнаружите, что отдельно извлеченный CSS также сжат.

Упаковать и опустошить

Мы обнаружили, что каждый раз, когда мы упаковываем, пока файл изменяется, файл будет добавлен Как автоматически очистить предыдущее содержимое пакета? webpack предоставляет подключаемый модуль clean-webpack-plugin. сначала импортировать

yarn add clean-webpack-plugin -D

Затем настройте файл пакета

const CleanWebpackPlugin = require('clean-webpack-plugin');

new CleanWebpackPlugin(), // 每次打包前清空

public path

Параметр конфигурации publicPath полезен в различных сценариях. Вы можете использовать его, чтобы указать базовый путь для всех ресурсов в приложении. добавить в конфигурацию упаковки

output: {
    publicPath : '/'
}

Добавлены @babel/polyfill, @babel/plugin-transform-runtime, core-js, @babel/runtime-corejs2, @babel/plugin-proposal-class-properties

yarn add @babel/polyfill -S

Добавьте следующую строку в запись вашего конфигурационного файла webpack:

 /*入口*/
entry: {
    app:[
        "@babel/polyfill",
        path.join(__dirname, '../src/index.js')
    ],
    vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
},

@babel/polyfill позволяет нам с удовольствием использовать несовместимые с браузером API es6 и es7. Но у него есть несколько недостатков:

  • Во-первых, мы использовали только несколько API, но представили весь
  • Во-вторых, это загрязнит глобальную

Далее, давайте сделаем некоторую оптимизацию и добавим

yarn add @babel/plugin-transform-runtime -D
yarn add core-js@2.6.5 -D
yarn add @babel/plugin-proposal-class-properties -D

yarn add @babel/runtime-corejs2 -S

После добавления настройте page.json и добавьте список браузеров, чтобы объявить эффективный браузер.

"browserslist": [
    "> 1%",
    "last 2 versions"
  ],

Модифицируем наш конфигурационный файл babel

{
    presets: [["@babel/preset-env",{
        useBuiltIns: "entry",
        corejs: 2
    }], "@babel/preset-react"],
    plugins: ["@babel/plugin-syntax-dynamic-import",'@babel/plugin-transform-runtime','@babel/plugin-proposal-class-properties']
}

useBuiltIns — это ключевой атрибут, он будет выполнять полифилл по запросу в зависимости от того, преобразует ли список браузеров новый синтаксис и полифиллирует ли новый API, используемый новым бизнес-кодом AP.

  • false : не включать полифилл, если импортировать '@babel/polyfill', все полифиллы будут загружены независимо от списка браузеров.
  • entry : enable, вам нужно вручную импортировать '@babel/polyfill', который будет отфильтровывать требуемые полифиллы в соответствии со списком браузеров
  • использование: нет необходимости вручную импортировать '@babel/polyfill' (его можно добавить, он будет удален во время разработки), и он будет основан на списке браузеров +

Примечание. После тестирования использование не может поддерживать IE, рекомендуется использовать запись, хотя это будут десятки тысяч.

@babel/plugin-transform-runtime и @babel/runtime-corejs2, первый используется в разработке, второй используется в рабочей среде. Основная функция: избегайте многократной компиляции вспомогательных функций: перенесенный код Babel должен использовать некоторые вспомогательные функции для достижения той же функции, что и исходный код. Это также может решить ситуацию, когда классы или методы экземпляра, предоставляемые @babel/polyfill, загрязняют глобальную область видимости.

@babel/plugin-proposal-class-properties — это то, что я пропустил раньше.Если вы хотите написать стрелочные функции или декораторы в классе, вам нужна его поддержка.

Запрос данных axios и Mock

Мы сейчас полностью разделены на фронт и тыл, фронтенд пишет, сервер пишет сервер, они связаны через интерфейс API. Однако зачастую серверный интерфейс пишется, а фронтенд не может отлаживать, только ждать. В настоящее время нам нужен наш Mock.js для предоставления данных. Mock.js автоматически перехватывает наши запросы AJAX и предоставляет множество случайно сгенерированных данных. (Обязательно прокомментируйте агент, который начинает настройку, иначе мы не сможем запросить наши MOCK-данные)

首先安装mockjs

yarn add mockjs -D

Затем создайте новый фиктивный каталог в корневом каталоге и создайте mock.js.

import Mock from 'mockjs';
 
Mock.mock('/api/user', {
    'name': '@cname',
    'intro': '@word(20)'
});

Смысл приведенного выше кода в том, чтобы перехватить /api/user и вернуть случайное китайское имя, строку из 20 букв. Затем импортируйте его в наш src/index.js.

import '../mock/mock.js';

Интерфейс и данные готовы, дальше пишем запрос на получение данных и их отображение.

首先引入axios

yarn add axios -S

Затем создайте редуктор, действие и страницу userInfo соответственно.

redux/actions/userInfo.js如下

import axios from 'axios';

export const GET_USER_INFO = "userInfo/GET_USER_INFO";

export function getUserInfo() {
    return dispatch=>{
        axios.post('/api/user').then((res)=>{
            let data = JSON.parse(res.request.responseText);
            dispatch({
                type: GET_USER_INFO,
                payload:data
            });
        })
    }
}
redux/reducers/userInfo.js如下

import { GET_USER_INFO } from 'actions/userInfo';


const initState = {
    userInfo: {}
};

export default function reducer(state = initState, action) {
    switch (action.type) {
        case GET_USER_INFO:
            return {
                ...state,
                userInfo: action.payload,
            };
        default:
            return state;
    }
}
pages/userInfo/index.js如下

import React, {PureComponent} from 'react';
import {connect} from 'react-redux';
import {getUserInfo} from "actions/userInfo";

class UserInfo extends PureComponent {

    render() {
        const { userInfo={} } = this.props.userInfo;
        return (
            <div>
                {
                    <div>
                        <p>用户信息:</p>
                        <p>用户名:{userInfo.name}</p>
                        <p>介绍:{userInfo.intro}</p>
                    </div>
                }
                <button onClick={() => this.props.getUserInfo()}>请求用户信息</button>
            </div>
        )
    }
}

export default connect((userInfo) => userInfo, {getUserInfo})(UserInfo);

Затем добавьте нашу userInfo в глобально уникальное состояние, сохраните внутри,

store.js

import userInfo  from 'reducers/userInfo';

let store = createStore(combineReducers({counter, userInfo}));

Наконец, вы можете добавлять новые маршруты и меню.

router.js

const UserInfo = loadable({
    loader: () => import('pages/UserInfo'),
    loading: Loading,
    timeout: 10000, // 10 seconds
})

<Route path="/userinfo" component={UserInfo}/>
components/Nav/index.js

<li><Link to="/userinfo">UserInfo</Link></li>

Запустите, нажмите кнопку запроса информации и найдите ошибку: Действия должны быть простыми объектами. Используйте специальное ПО промежуточного слоя для асинхронных действий. Это предложение указывает, что действия должны быть объектом действия. Если вы хотите использовать асинхронность, вы должны использовать ПО промежуточного слоя.

промежуточное ПО redux-thunk

Сначала импортируем

yarn add redux-thunk -S

Затем мы используем метод applyMiddleware, предоставленный redux, для запуска промежуточного программного обеспечения redux-thunk, чтобы действия поддерживали асинхронные функции.

import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';

let store = createStore(combineReducers({counter, userInfo}), applyMiddleware(thunkMiddleware));

Затем перезапускаем его, и обнаруживаем, что данные получены.

развертывать

Чтобы проверить, пригодны ли упакованные нами файлы, существует простой экспресс-сервис. Сначала создайте новый каталог сервера в корневом каталоге и выполните следующие команды в этом каталоге.

npm init 

yarn add nodemon express -D
  • Express — это относительно простой в использовании фреймворк узлов.
  • nodemon — это вспомогательный инструмент для разработки узлов, который может обновлять код nodejs без перезапуска, что очень просто в использовании. После установки зависимостей добавляем наш файл express.js для записи службы узла
var express = require('express');
var path = require('path');
var app = express();

app.get('/dist*', function (req, res) {
   res.sendFile( path.join(__dirname , "../" + req.url));
})
app.use(function (req, res) {
	res.sendFile(path.join( __dirname , "../dist/" + "index.html" ));
}) 
 
var server = app.listen(8081, function () {
  var host = server.address().address
  var port = server.address().port
  console.log("应用实例,访问地址为 http://%s:%s", host, port)
})

Я не буду вдаваться в подробности кода узла, вы можете найти туториалы онлайн. Тут главное запустить сервис с портом 8081, а потом сделать два перехвата.Первый перехват для всех обращений по адресу dist*, и он передается в файл запакованный под наш dist. Второй перехват — перехватывать все неверные адреса и пересылать их в наш index.html, что может решить проблему обновления 404.

Добавьте команду запуска в файл package.json в каталоге сервера и выполните ее.

"test": "nodemon ./express.js"
npm run test

После запуска посетите http://localhost:8081, и вы обнаружите, что многие модули вводят 404. Не паникуйте, здесь есть информация, упомянутая ранее - publicPath. мы меняем его на

publicPath : '/dist/',

Упаковав его один раз, вы обнаружите, что все нормально, служба нашего узла завершена, и упакованный код можно использовать в обычном режиме.

конец

Здесь, в этом здании семейное руководство по реагированию на реагирование. Первый написанный, суммирует некоторые места не очень хорошие. Не много говорил, поместите некоторую информацию для вашей справки.

Специальное примечание

Я также являюсь членом тысяч передовых бизнесменов.Некоторые вопросы задают мои знания вслепую или у меня нет времени отвечать.Пожалуйста, простите меня, спасибо! ! !

Кроме того, это руководство в основном предназначено для новичков и новых друзей, которые переходят на React с других технологических стеков в качестве справочного материала и могут иметь относительно полное представление о React Framework. Другие оптимизации и поддержка здесь не добавляются.

Рекомендуется, чтобы этот учебник предназначен только для справочного обучения и не может использоваться в качестве высококачественной среды разработки для проектов.

Код будет снова протестирован и загружен на github на следующей неделе.

адрес git (настолько большой, чтобы его можно было увидеть)