предисловие
- В проекте используется react 16.8.x, typescript 3.5.3.
- тогда также используйте
koa2+typescriptвзял одинПростойФоновая служба API используется только для проверки использования инкапсулированного Axios Api, а также для личных игровых потребностей node.js🙃, не требует операций с базой данных и т. д. . . код можно поставить штампздесь - Кстати, я обновил webpack4
- Тогда это просто пустой шаблон для проверки чего-то, всего несколько простых демонстрационных страниц, остальные удалены. . .
- Исходный код этого проекта можно увидетьздесь
- Обновление: [2019-09-05]: электрон, вы можете видетьздесь
- Обновление: [2019-09-09]: сторонние ресурсы используют CDN (см. 13, сборка)
- Обновление: [2019-11-08]: Управление состоянием
redux+rematchзаменитьmobx, предварительная загрузка ресурсов, предварительная выборка и т. д.
1. Создайте проект
Официальная демонстрация antd здесь не используется., но добавьте antd в обычный проект react+typescript, а затем преобразуйте его
Почему бы не использовать официальную демо-версию antd?Потому что я могу использовать его после того, как попробовал, но веб-пакет не может установить псевдоним, всегда есть проблема, поэтому мне это не нужно. . .
create-react-app project --typescript
исходная структура:
.
├── api
├── assets
├── components
├── lang
├── routes
├── store
├── utils
├── views
├── App.scss
├── App.test.tsx
├── App.tsx
├── index.scss
├── index.tsx
├── router.tsx
└── setupProxy.js
2. Машинопись
tsconfig.json:
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "preserve",
"moduleResolution": "node",
"rootDir": ".",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"paths": {
"@/*": ["./*"]
}
},
"awesomeTypescriptLoaderOptions": {
"useBabel": true,
"useCache": false,
"emitRequireType": false
},
"includes": [
"src"
],
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts",
"public/"
]
}
.babelrc
{
"presets": [
"react-app"
],
"plugins": [
"transform-decorators-legacy",
[
"import",
{
"libraryName": "antd-mobile",
"style": "css"
}
]
]
}
3. Обновите webpack4.x
добавить в webpack.config.dev.jsmodeПоле:mode: 'development'
добавить в webpack.config.prod.jsmodeПоле:mode: 'production'
Связанные модули, которые необходимо обновить:
yarn upgrade **обновить или напрямуюyarn add ** -Dтакже может
file-loaderfork-ts-checker-webpack-pluginhtml-webpack-plugin@nextreact-dev-utilsurl-loaderwebpackwebpack-cliwebpack-dev-serverwebpack-manifest-plugin
Частичный контроль качества
-
Ошибка компиляции: webpack не является функцией
Обновите соответствующий плагин выше, затем script/start.js:
const compiler = createCompiler(webpack, config, appName, urls, useYarn);Измените его на:
const compiler = createCompiler({webpack, config, appName, urls, useYarn}); -
Ошибка компиляции: this.htmlWebpackPlugin.getHooks не является функцией.
Уведомление
html-webpack-plugin@nextЭтот плагин должен добавить @next
config/webpack.comfig.dev.js, config/webpack.config.prod.js:
new InterpolateHtmlPlugin(env.raw)Измените его на:
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw) -
После упаковки сообщается об ошибке загрузки чанка.
config/paths.js:
function getServedPath(appPackageJson) { const publicUrl = getPublicUrl(appPackageJson); const servedUrl = envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); return ensureSlash(servedUrl, true); }положить
'/'изменить на'./'Только что -
Сообщить @types/tapable @types/html-minifier @types/webpack не существует
yarn add @types/tapable @types/html-minifier @types/webpack
4. и тд
yarn add antd
нагрузка по требованию
-
ts/tsxиспользоватьawesome-typescript-loaderАнализ этого загрузчика -
antdCSS компонента загружается по требованию и используетсяbabel-plugin-importэтот плагин
yarn add awesome-typescript-loader babel-plugin-import
// webpack.config.dev.js, webpack.config.prod.js
{
test: /\.(ts|tsx)$/,
include: paths.appSrc,
loader: 'awesome-typescript-loader',
exclude: /node_modules/,
options: {
babelOptions: {
"presets": ["react"],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": "css"
}
]
]
}
}
},
5. Контроль маршрутизации/авторизации
Маршруты загружаются и используются по запросу@loadable/component
Если вы сообщаете об ошибке @types/xxx, просто следуйте инструкциям по установке, если нет, установите вручную.common.d.tsдобавить одинdeclare module '@loadable/component';
yarn add @loadable/component
маршрутизация
- Маршрутизация в приложении
Благодаря следующему мы можем реализовать метод написания вложенных маршрутов внутри приложения в Vue.
props.childrenЭквивалент Vuerouter-view,ПотомHeaderПодождите, пока глобальный компонент будет смонтирован только один раз
// src/router.tsx
...
<AuthRoute
path='/'
render={() => (
<App>
<Switch>
{routes.map(route => route)}
</Switch>
</App>
)}
/>
...
- Маршрутизация не зависит от приложения
aloneComp
// src/router.tsx
<Switch>
{
aloneComp.map(route => route)
}
<AuthRoute
path='/'
render={() => (
<App>
<Switch>
{routes.map(route => route)}
</Switch>
</App>
)}
/>
</Switch>
// src/App.tsx
...
public render() {
return (
<div className={style.app}>
<Header />
{ this.props.children }
</div>
);
}
управление маршрутом
- Маршрут унифицированное управление
// src/routes/index.tsx
import login from './login-register';
import home from './home';
/**
* 使用这个组件 '@/routes/auth-route',代替官方 Route,控制需要登录权限的路由
*/
export default [
...login,
...home
]
- модуль маршрутизации
// src/routes/home.tsx
import AuthRoute from '@/routes/auth-route';
import * as React from 'react';
import Loadable from '@loadable/component';
// home
export default [
<AuthRoute
key="home"
exact={true}
path="/"
component={Loadable(() => import('@/views/home'))}
/>,
<AuthRoute
key="home"
exact={true}
path="/home"
component={Loadable(() => import('@/views/home'))}
/>
]
- Запись маршрутизации router.tsx
Он разделен на маршруты под приложением и маршруты, независимые от приложения; в некоторых случаях, если все страницы имеют одну и ту же оболочку приложения, нет необходимости их разделять.
// src/router.tsx
import * as React from 'react';
import { HashRouter, Switch } from 'react-router-dom';
import AuthRoute from '@/routes/auth-route';
import Loadable from '@loadable/component';
import PageRoutes from './routes';
import login from '@/routes/login-register';
// 使用 import { lazy } from '@loadable/component';
// lazy()会有警告,跟React.lazy()一样的警告
const App = Loadable(() => import('./App'));
const ErrComp = Loadable(() => import(/* webpackPrefetch: true */ './views/err-comp'));
const AppComp = () => {
// 独立在 app 之外的路由
const aloneComp = [
...login
];
const ErrRoute =
<AuthRoute
key='err404'
exact={true}
path='/err404'
component={ErrComp}
/>;
const NoMatchRoute =
<AuthRoute
key='no-match'
component={ErrComp}
/>;
const routes = [...PageRoutes, ErrRoute, NoMatchRoute];
return (
<Switch>
{
aloneComp.map(route => route)
}
<AuthRoute
path='/'
render={() => (
<App>
<Switch>
{routes.map(route => route)}
</Switch>
</App>
)}
/>
</Switch>
);
}
export default function Router() {
return (
<HashRouter>
<AppComp />
</HashRouter>
);
}
- Запись проекта src/index.tsx
// src/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import store from '@/store';
import AxiosConfig from './api';
import Router from './router';
import './index.scss';
// import registerServiceWorker from './registerServiceWorker';
const Loading = () => (<div>loading...</div>);
AxiosConfig(); // 初始化 axios
ReactDOM.render(
<React.Suspense fallback={<Loading />}>
<Provider {...store}>
<Router />
</Provider>
</React.Suspense>,
document.getElementById('root') as HTMLElement
);
// registerServiceWorker();
Контроль разрешений на вход
использоватьjs-cookieпакет, сохраните токен (sessionId?), возвращенный внутренним интерфейсом после входа в систему, в поле «auth» в файле cookie.
// src/routes/auth-route.tsx:
import * as React from 'react';
import { ComponentProps } from 'react';
import { Route, Redirect, RouteProps } from 'react-router';
import * as Cookies from 'js-cookie';
export interface AuthRouteProps extends RouteProps {
key?: string|number,
path?: string,
auth?: boolean, // 是否需要权限
redirectPath?: string, // 重定向后的路由
render?: any,
component?: ComponentProps<any>
}
const initialProps = {
key: 1,
path: '/login',
auth: true,
component: () => <div />
};
/**
* 权限控制处理路由
*/
const AuthRoute = (props: AuthRouteProps = initialProps) => {
const { auth, path, component, render, key, redirectPath } = props;
if (auth && !Cookies.get('auth')) {
// console.log('path: ', path);
return (
<Route
key={key}
path={path}
render={() =>
<Redirect to={{
pathname: redirectPath || '/login',
search: '?fromUrl='+path
}} />
}
/>
)
}
return (
<Route
key={key}
path={path}
component={component}
render={render}
/>
)
}
export default AuthRoute;
6. Управление API
axios
yarn add axios
конфигурация axios, перехват запроса/ответа
// src/api/index.ts
import axios, { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios';
import { message } from 'antd';
import * as Cookies from 'js-cookie';
import * as NProgress from 'nprogress';
axios.defaults.timeout = 10000;
axios.defaults.baseURL = process.env.NODE_ENV === 'production'
? 'http://192.168.0.5:2333' // 这里设置实际项目的生产环境地址
: '';
let startFlag = false; // loadingStart的标志
// 拦截器
export default function AxiosConfig() {
// 请求拦截
axios.interceptors.request.use((config: AxiosRequestConfig) => {
if (config.data && config.data.showLoading) {
// 需要显示loading的请求
startFlag = true;
NProgress.start();
}
// 请求 access_token,登录后每个请求都带上
if (Cookies.get('auth')) {
config.headers.Authorization = Cookies.get('auth');
}
if (config.params) config.params._t = Date.now();
return config;
}, (err: AxiosError) => {
if (startFlag) {
startFlag = false;
NProgress.done();
}
return Promise.reject(err);
});
// 响应拦截
axios.interceptors.response.use((res: AxiosResponse) => {
if (startFlag) {
startFlag = false;
NProgress.done();
}
return res.data;
}, (err: AxiosError) => {
// 服务器错误
if (err.response && (err.response.status+'').startsWith('5')) {
message.error('请求出错!')
}
if (startFlag) {
startFlag = false;
NProgress.done();
}
return Promise.reject(err);
})
}
модуль API
// src/api/test-api.ts
import axios from 'axios';
// 获取文件
const api = {
// 示例:
// get只有params才会作为请求参数
// 其他请求方式如:POST,PUT,PATCH,data作为请求参数
testApi: (params: any = {}) => {
// post
// return axios.post('/api/file/uploadFile', params);
// get
return axios.get('/api/file/getFile', {
params,
data: { showLoading: true }
});
}
};
export default api;
использование API
import Api from '@/api/test-api';
...
Api.testApi(params).then((res: any) => {...});
-7.Использовать рематч для управления состоянием (рематч заброшен и заменен на mobx)
из-заredux v7.1.0недавно добавленныйuseSelector, useDispatchДождитесь хуков, обновитеreact-reduxВерсия может быть использована, следующее увеличит использованиеuseSelector, useDispatchверсия
yarn add @rematch/core react-redux
управление магазином
// src/store-rematch/index.ts
import { init, RematchRootState } from '@rematch/core';
import * as models from './models/index';
// 缓存列表
const cacheList = ['common'];
const stateCache = sessionStorage.getItem('store-rematch');
// 初始化 state
const initialState = (stateCache && JSON.parse(stateCache)) || {};
const store = init({
models,
redux: {
initialState
}
});
// 监听每次 state 的变化
store.subscribe(() => {
const state = store.getState();
let stateData = {};
Object.keys(state).forEach(item => {
if (cacheList.includes(item)) {
stateData[item] = state[item];
}
});
sessionStorage.setItem('store-rematch', JSON.stringify(stateData));
});
export type Store = typeof store;
export type Dispatch = typeof store.dispatch;
export type iRootState = RematchRootState<typeof models>;
export default store;
models
// src/store-rematch/models/indes.ts
import { createModel } from '@rematch/core';
// import detail from './detial';
export interface ICommonState {
appName: string,
isMobile: boolean,
count: number,
countAsync: number
}
const initialState: ICommonState = {
appName: 'react-ts-mdnote',
isMobile: false,
count: 0,
countAsync: 0
};
const common = createModel({
state: initialState,
reducers: {
setIsMobile(state: ICommonState, payload: boolean) {
return {
...state,
isMobile: payload
}
},
addCount(state: ICommonState) {
return {
...state,
count: state.count + 1
}
},
setCount(state: ICommonState, payload: number) {
return {
...state,
countAsync: payload
}
}
},
effects: (dispatch) => ({
async setCountAsync(payload, rootState) {
await new Promise(resolve => setTimeout(resolve, 1000))
dispatch.common.setCount(payload)
}
})
});
export {
common,
// detail
}
используемые компоненты
- обычный
connect + mapState + mapDispatchнаписание
// src/views/home/index.tsx
import * as React from 'react';
import { connect } from 'react-redux';
import { iRootState, Dispatch } from '@/store-rematch';
import { Button } from 'antd';
import styles from './home.scss';
interface IProps {
[prop: string]: any
}
function Home(props: IProps) {
return (
<div className={styles.home}>
<div className={styles.content}>
<p>react-ts-antd-template</p>
<p className={styles.count}>
count: { props.count }  
<Button onClick={props.addCount}>count++</Button>
</p>
<p className={styles.count}>
countAsync: { props.countAsync }  
<Button onClick={props.setCountAsync}>countAsync</Button>
</p>
</div>
</div>
)
}
const mapState = (state: iRootState) => {
return {
count: state.common.count,
countAsync: state.common.countAsync
}
}
const mapDispatch = (dispatch: Dispatch) => {
return {
addCount: () => dispatch({ type: 'common/addCount' }),
setCountAsync: () => dispatch({ type: 'common/setCountAsync', payload: new Date().getSeconds() }),
}
}
export default connect(mapState, mapDispatch)(Home);
react-reduxДобавлены хуки:useSelector, useDispatchнаписание
import * as React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { iRootState, Dispatch } from '@/store-rematch';
import { Button } from 'antd';
import styles from './home.scss';
interface IProps {
[prop: string]: any
}
function Home(props: IProps) {
const dispatch: Dispatch = useDispatch();
const { count, countAsync } = useSelector((state: iRootState) => state.common);
return (
<div className={styles.home}>
<div className={styles.content}>
<p>react-ts-antd-template</p>
<p className={styles.count}>
count: { count }  
<Button onClick={() => dispatch({ type: 'common/addCount' })}>count++</Button>
</p>
<p className={styles.count}>
countAsync: { countAsync }  
<Button
onClick={() => dispatch({ type: 'common/setCountAsync', payload: new Date().getSeconds() })}
>countAsync</Button>
</p>
</div>
</div>
)
}
export default Home;
+7, управление состоянием мобх
yarn add mobx mobx-react
По сравнению с redux, mobx имеет несколько концепций и прост в написании и использовании; компоненты класса используют декораторы, а компоненты функций используют функции с тем же именем.
- @observable: объявить состояние данных
- @computed: вычисляемые свойства, вы можете получить необходимые данные из объекта или массива
- @action: функция действия, вы можете напрямую писать асинхронные функции
- Работание: Обратите внимание, что нет
@, Не декоратор; в@actionМодификация внутри декорированной функцииstate, как показано нижеsetTimeoutИзменить данные в - поток: возвращает функцию генератора генератора с
function */yieldзаменятьasync/await(эти два на самом деле их синтаксический сахар), нет необходимости использовать@action/runInAction - @inject('homeStore'): будет
homeStoreвнедрить в компонент - @observer: функцию/декоратор можно использовать для превращения компонентов React в реактивные компоненты. Он обертывает функцию рендеринга компонента с помощью mobx.autorun, чтобы гарантировать, что любые изменения данных, используемых в рендеринге компонента, могут привести к обновлению компонента. наблюдатель предоставляется отдельным пакетом mobx-react.
Другая конфигурация:
- Скачать плагин
yarn add babel-plugin-transform-decorators-legacy -D - Затем в .babelrc: используйте декоратор
"plugins": ["transform-decorators-legacy"] - tsconfig.json: использовать декораторы
"compilerOptions": { "experimentalDecorators": true, }
7.1 Вход в проект
использоватьProviderвключить элементы
import { Provider } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import store from './store';
import AxiosConfig from './api';
import Router from './router';
import './index.scss';
import registerServiceWorker from './registerServiceWorker';
const Loading = () => (<div>loading...</div>);
AxiosConfig(); // 初始化 axios
ReactDOM.render(
<React.Suspense fallback={<Loading />}>
<Provider {...store}>
<Router />
</Provider>
</React.Suspense>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
7.2 Модули
// src/store/home.ts
import * as mobx from 'mobx';
// 禁止在 action 外直接修改 state
mobx.configure({ enforceActions: "observed"});
const { observable, action, computed, runInAction, autorun } = mobx;
let cache = sessionStorage.getItem('homeStore');
// 初始化数据
let initialState = {
count: 0,
data: {
time: '2019-11-08'
},
};
// 缓存数据
if (cache) {
initialState = {
...initialState,
...JSON.parse(cache)
}
}
class Home {
@observable
public count = initialState.count;
@observable
public data = initialState.data;
@computed
public get getTime() {
return this.data.time;
}
@action
public setCount = (_count: number) => {
this.count = _count;
}
@action
public setCountAsync = (_count: number) => {
setTimeout(() => {
runInAction(() => {
this.count = _count;
})
}, 1000);
}
// public setCountFlow = flow(function *(_count: number) {
// yield setTimeout(() => {}, 1000);
// this.count = _count;
// })
}
const homeStore = new Home();
autorun(() => {
// 数据变化后触发,数据缓存
const obj = mobx.toJS(homeStore);
sessionStorage.setItem('homeStore', JSON.stringify(obj));
})
export type homeStoreType = typeof homeStore;
export default homeStore;
7.3 Кэш
Используйте sessionStorage здесь, измените на другие необязательные
При кэшировании данных вы можете сопоставлять определенные ключи для кэширования вместо всех данных по мере необходимости;
-
Данные инициализации
Когда данные инициализируются, если в кеше есть данные, данные по умолчанию перезаписываются кешированными данными.
let cache = sessionStorage.getItem('homeStore'); // 初始化数据 let initialState = { count: 0, data: { time: '2019-11-08' }, }; // 缓存数据 if (cache) { initialState = { ...initialState, ...JSON.parse(cache) } } -
Мониторинг изменений данных
автозапуск будет выполняться каждый раз при изменении данных, а затем
homeStoreПреобразуйте его в объект js (только состояние) и сохраните его в кеше.const homeStore = new Home(); autorun(() => { // 数据变化后触发,数据缓存 const obj = mobx.toJS(homeStore); sessionStorage.setItem('homeStore', JSON.stringify(obj)); })
7.4 Выход управления модулем
// src/store/index.ts
import homeStore from './home';
/**
* 使用 mobx 状态管理
*/
export default {
homeStore
}
7.5 Использование компонентов
Использование декоратора в классе делает свое дело,injectВнедрить соответствующий модуль, который может быть несколько разinject;
Уведомление
@inject('homeStore') @observerПорядок этих двух, иначе будет предупреждение
// src/views/home/index.tsx
import { observer, inject } from 'mobx-react';
import { homeStoreType } from '@/store/home';
...
interface IProps extends RouteComponentProps {
history: History,
homeStore: homeStoreType
}
@inject('homeStore')
@observer
class Home extends React.Component<IProps> {
...
public componentDidMount() {
this.props.homeStore.setCount(2);
console.log(this.props.homeStore.count); // 2
}
...
}
8. Междоменный прокси
использоватьhttp-proxy-middlewareплагин
yarn add http-proxy-middleware
Новый scr/setupProxy.js
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
proxy('/', {
target: 'http://192.168.0.5:2333',
changeOrigin: true
})
);
};
В script/start.js используйте:
существует
const devServer = new WebpackDevServer(compiler, serverConfig);
После этого добавьте следующий код (если ниже можно проксировать, то добавлять не нужно)
require('../src/setupProxy')(devServer);
9, css-модуль, глобальные переменные scss
конфигурация вывода класса:[local]__[hash:base64:6], вывод выглядит так:content__1f1Aqs, подробности см.здесь
глобальные переменные sass используют этот загрузчикsass-resources-loader,
Настроить загрузчик, а потом в этом файлеsrc/utils/variable.scssНапишите переменную, и тогда вы можете использовать ее с удовольствием
yarn add sass-resources-loader
// webpack.config.dev.js, webpack.config.prod.js
{
test: /\.(scss|less)$/,
exclude: [/node_modules/],
use: [
{
loader: require.resolve('style-loader'),
},
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
localIdentName: '[local]__[hash:base64:6]'
}
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('sass-loader'),
},
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, './../src/utils/variable.scss'),
],
}
}
]
},
10. Список поддержания активности
можно смотретьздесь
11. Компоненты высшего порядка и withRouter
В основном, когда используются несколько компонентов более высокого порядкаprops перевод типатребует внимания
Context.Provider
// src/App.tsx
import * as React from 'react';
import Header from '@/components/header';
import Sidebar from '@/components/sidebar';
import Footer from '@/components/footer';
import styles from './App.scss';
import { RouteComponentProps, withRouter } from 'react-router';
interface IProps extends RouteComponentProps {
[prop: string]: any
}
export interface IState {
timer?: any
}
export type State = Readonly<IState>;
export interface IAppContext {
appname: string
}
const defaultContext: IAppContext = { appname: 'react-antd-ts' };
export const AppContext = React.createContext(defaultContext);
class App extends React.Component<IProps, State> {
public readonly state: State = {};
constructor(props: IProps) {
super(props);
}
public render() {
return (
<div className={styles.app}>
<AppContext.Provider value={defaultContext}>
<Header text="tteexxtt" />
<Sidebar />
{ this.props.children }
<Footer />
</AppContext.Provider>
</div>
);
}
}
export default withRouter(App);
Оболочка Context.Consumer
Вместо этого вы также можете использовать useContext, вам не нужна следующая потребительская упаковка.
// src/components/withAppContext/index.tsx
import * as React from 'react';
import { AppContext, IAppContext } from '@/App';
// 高阶组件:AppContext Consumer包装
// 使用时包在最外层,如 withAppContext<IProps>(withRouter(Header));
function withAppContext<T>(Component: React.ElementType) {
// T: 泛型,传递 Component 的 props 类型,被包装的组件在父组件使用时智能提示
// 但是需要和 withRouter 的类型分开,
// 因为 withRouter 不会传递除 history/location/match 之外的 props
return (props: T) => {
return (
<AppContext.Consumer>
{
(appcontext: IAppContext) => <Component {...props} {...appcontext} />
}
</AppContext.Consumer>
);
}
}
export default withAppContext;
Использование компонентов
Уведомление:
1,
withRouterне проходит кромеhistory/location/matchза пределамиprops, Так вот с самим компонентомpropsтип раздельный;
2. Используйте
withAppContextПередаваемые дженерики являются реквизитами самого компонента: т.е. IProps
// src/components/header/index.tsx
import * as React from 'react';
import withAppContext from '@/components/withAppContext';
import { withRouter, RouteComponentProps } from 'react-router';
import styles from './header.scss';
const { useEffect } = React;
interface IProps {
text: string,
[prop: string]: any
}
// withRouter不会传递除 history/location/match 之外的 props,
// 所以这里与组件本身的 props 类型分开
type IPropsWithRoute = IProps & RouteComponentProps;
function Header(props: IPropsWithRoute) {
useEffect(() => {
console.log(props);
}, []);
return (
<section className={styles.header}>
<div className="center-content">
<div>LOGO</div>
<div>HEADER, { props.appname }, {props.text}</div>
</div>
</section>
);
}
// withRouter不会传递除 history/location/match 之外的 props,
// 所以这里使用组件本身的 props:即 IProps
export default withAppContext<IProps>(withRouter(Header));
12. Интернационализация
использовать реакцию-интл
yarn add react-intl @types/react-intl
Использовать IntlProvider в приложении
// src/App.tsx
import { IntlProvider } from 'react-intl';
import messages from '@/lang';
...
class App extends React.Component<Props, State> {
public readonly state: State = {
lang: Cookies.get('lang') || 'zh'
};
constructor(props: Props) {
super(props);
}
public onLangChange(locale: string) {
Cookies.set('lang', locale);
this.setState({ lang: locale });
}
public render() {
// console.log(this.props);
const { lang } = this.state;
return (
<div className={styles.app}>
<IntlProvider key="intl" locale={lang} messages={messages[lang]}>
<AppContext.Provider value={defaultContext}>
<Header text="tteexxtt" onLangChange={(locale: string) => this.onLangChange(locale)} />
<Sidebar />
{ this.props.children }
<Footer />
</AppContext.Provider>
</IntlProvider>
</div>
);
}
}
...
языковой файл
ланг вход
// src/lang/index.ts
import en from './en_US';
import zh from './zh_CN';
export default {
en,
zh
};
Сообщения на конкретном языке
первоначально воображаемый
Vueиспользуется внутриi18nТаким образом, языковой модуль имеет еще один слой, но структура плагина, похоже, не разрешена (может быть, нужно установить), поэтому вы можете только сплющить модуль расширения, а затем нижемодуль сообщенийИмя ключа внутри обрабатывается
// src/lang/zh_CN/index.ts
import home from './home';
// import detail from './detail';
export default {
...home,
// ...detail
};
модуль сообщений
Обратите внимание на имя ключа и временно используйте этот метод для реализации многоязычности по модулю.
// src/lang/zh_CN/home.ts
const home = {
'home.home': '首页',
'home.list': '列表',
'home.login': '登录'
};
export default home;
Использование компонентов
react-intlВ дополнение к этому многоязычному пакетуFormattedMessageКроме того, есть другие компоненты, используемые для реализации отображения разницы между суммой, валютой, датой и т. д., поэтому я не буду писать об этом здесь, просто следуйте документу, если вам нужно.
// src/components/sidebar/index.tsx
import { FormattedMessage } from 'react-intl';
...
<FormattedMessage id="home.home" />
переключить язык
// src/components/header/index.tsx
...
import Cookies from 'js-cookie';
const { useEffect, useMemo } = React;
interface IProps {
text: string,
onLangChange: (locale: string) => void,
[prop: string]: any
}
// withRouter不会传递除 history/location/match 之外的 props,
// 所以这里与组件本身的 props 类型分开
type IPropsWithRoute = IProps & RouteComponentProps;
function Header(props: IPropsWithRoute) {
const lang = useMemo(() => {
return Cookies.get('lang') || 'zh';
}, [Cookies.get('lang')]);
return (
<section className={styles.header}>
...
<div className={styles.langsection}>
<span
className={`${styles.lang} ${lang === 'zh' ? styles.active : ''}`}
onClick={() => props.onLangChange('zh')}
>中文</span>
<span
className={`${styles.lang} ${lang === 'en' ? styles.active : ''}`}
onClick={() => props.onLangChange('en')}
>English</span>
</div>
...
</section>
);
}
...
13. Построить
выход
использовать
chunkhashЕсли каждая сборка будет генерировать хэш, что приведет к тому же содержимому, но с другим именем файла, поэтому измените его наcontenthashГенерировать хэш по содержимому, тогда значение хэша связано с содержимым, лучшее кеширование, но это неизбежно приведет к увеличению времени построения, но оно того стоит
- Имя файла: измените имя файла в выводе
chunkhash->contenthash,Такие как:
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
- разделение кода
optimization: {
splitChunks: {
chunks: 'all'
}
},
tree-shaking
В документации по веб-пакету есть инструкции по настройке.mode: 'production', но файл после того, как я построю здесь, откройтеwebpack moduleБудет жаловаться; но наборmode: 'development'После этого к нему можно нормально обращаться, но файл сравнивается сproductionДелать больше, , тогда смысла нет, так что эта частьпока не занят. . .
TypeError: Cannot read property 'call' of undefined
в пакете.json
Добавить к"sideEffects": false,
в webpack.prod.js
optimization: {
...
// tree shaking,与 package.json 中 "sideEffects": false 配合使用
usedExports: true
}
CDN стороннего ресурса
В настоящее время только строительство использует ресурс CDN для внедрения, и нет никакой разницы в стадии разработки.
react-router-dom сообщит об ошибке, если есть проблема, временно недоступен
Временно обработайте это вручную, или вы можете использовать HtmlWebpackPlugin для автоматической обработки.
Формат:包名: 导出变量名
- webpack использует внешние:
externals: {
'axios': 'axios',
'lodash' : {
commonjs: 'lodash',
amd: 'lodash',
root: '_' // 指向全局变量
},
'react': 'React',
'react-dom': 'ReactDOM',
'react-router': 'ReactRouter',
// 'react-router-dom': 'ReactRouterDOM',
'mobx': 'mobx',
'react-mobx': 'ReactMobx',
},
- Добавьте ссылки CDN на сторонние ресурсы в public/index.html.
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-router/5.0.1/react-router.min.js"></script>
<!-- <script src="https://cdn.bootcss.com/react-router-dom/5.0.1/react-router-dom.min.js"></script> -->
<script src="https://cdn.bootcss.com/mobx/4.14.0/mobx.umd.min.js"></script>
<script src="https://cdn.bootcss.com/mobx-react/5.2.0/custom.min.js"></script>
<script src="https://cdn.bootcss.com/lodash.js/4.17.15/lodash.core.min.js"></script>
наконец
- Вещи, используемые в проекте, в основном указаны выше, а другие вещи будут обновлены и добавлены позже;
- Некоторая часть предыдущего кода была написана в первые дни, а новые вещи были добавлены позже, поэтому он отличается от некоторых из следующих функций, но, как правило, в соответствии с предыдущим методом написания проблем не возникает; то есть новые функции нуждаются переписать исходную часть кода
- Кроме того, вы можете использовать только одну конфигурацию разработки/производства веб-пакетов, а затем использовать слияние веб-пакетов для входа в нее.Все веб-пакеты в этой статье изменены на основе старых файлов, и некоторые вещи могут быть избыточными. . .
- React Hooks уже очень полезны, почти не нужно писать компоненты класса
- Обратите внимание на передачу реквизита нескольких компонентов более высокого порядка.
- React используется уже несколько месяцев, и это все, о чем я могу думать, другие продвинутые продукты временно недоступны. . .