предисловие
Всем привет! я фронтендбезымянный
задний план
Каждый день я пишу бизнес без перерыва.React-проекты делаются один за другим.Много лет мне приходится создавать новый React-проект, копировать старый проект напрямую, затем удалять старую бизнес-логику, а также пересматривать и сокращать новая структура проекта.
Результатом этого является:
- Создание проекта занимает слишком много времени
- Исправление и снижение нестабильности структуры проекта
- Не способствует итеративной структуре оптимизации
- ...
Поэтому мне пришла в голову идея создать свой собственный набор фреймворков проекта React из коробки.
В этой статье сначала рассказывается о функции шаблона проекта, а затем о том, как создать и спроектировать проект.
Структура проекта
react-project-template
├── .babelrc # babel配置
├── Webpack # webpack公用配置目录
│ │ ├──plugins # 公用插件集合
│ │ ├──resolve # webpack resolve配置
│ │ ├──utils # webpack 工具类
│ │ ├──variable # webpack 变量配置
│ ├── webpack.base.js # Webpack 基础配置文件
│ ├── webpack.dev.js # Webpack 开发环境配置文件
│ └── webpack.prod.js # Webpack 生产环境配置文件
├── yarn.lock # 锁定 npm 包依赖版本文件
├── package.json
├── postcss.config.js # 自动兼容 CSS3 样式配置文件
├── .editorconfig # IDE格式化规范
├── .eslintignore # eslint忽略文件配置
├── .eslintrc.js # eslint配置文件
├── .prettierignore # prettierc忽略文件配置
├── .prettierrc # prettierc配置文件
├── .husky # 配置git提交钩子
├── .commitlint.config.js # 配置git提交规范
├── tsconfig.eslint.js # eslint检查typescript配置项配置文件
├── eslintError.html # eslint报告文件
├── public # 存放html模板
├── README.md
├── src
│ ├── assets # 存放会被 Webpack 处理的静态资源文件:一般是自己写的 js、css 或者图片等静态资源
│ │ ├── fonts # iconfont 目录
│ │ ├── images # 图片资源目录
│ │ ├── css # 全局样式目录
│ │ │ ├── common.scss # 全局通用样式目录
│ │ │ ├── core.scss # 全局sass 变量目录,直接使用,不需要引用,全局已统一引入。
│ │ │ └── init.scss # 全局初始化css
│ │ └── js # 全局js
│ ├── common # 存放项目通用文件
│ │ ├── Resolution.ts # 布局适配配置中心
│ │ └── AppContext.ts # 全局App上下文
│ ├── components # 项目中通用的业务组件目录
│ ├── config # 项目配置文件
│ ├── pages # 项目页面目录
│ ├── typings # 项目中d.ts 声明文件目录
│ ├── types # 项目中声明文件
│ │ ├── service # 项目中服务相关声明文件
│ │ ├── enum.ts # 项目中枚举类型
│ │ ├── IContext.ts # 全局App上下文声明
│ │ ├── IRedux.ts # redux相关声明
│ │ └── IRouterPage.ts # 路由相关声明
│ ├── uiLibrary # 组件库
│ ├── routes # 路由目录
│ │ ├── index.tsx # 路由配置入口文件
│ │ └── RouterUI.tsx # 路由转换
│ ├── services # 和后端相关的文件目录
│ │ ├── api # 调用后端接口定义目录
│ │ │ ├── index.ts
│ │ ├── axios.ts # 基于 axios 二次封装
│ │ ├── BaseService.ts # 基础请求服务类型
│ │ ├── ServerResponseManager.ts # 服务返回统一管理
│ │ ├── serviceConfig.ts # 服务地址配置文件
│ ├── store # redux 仓库
│ │ ├── actionCreaters # action创建与分发绑定
│ │ ├── action # 项目中action
│ │ ├── reducers # 项目中reducers
│ │ │ ├──history # 项目中路由相关history
│ │ ├── index.ts # 全局 store 获取
│ │ ├── connect.ts # react 页面与store 连接
│ ├── utils # 全局通用工具函数目录
│ ├── App.tsx # App全局
│ ├── index.tsx # 项目入口文件
│ ├── index.scss # 项目入口引入的scss
└── tsconfig.json # TS 配置文件
адрес проекта
Если вы считаете, что приведенная выше структура проекта шаблона React вам нравится, вы можете перейти к исходному коду.react-project-template
А также сделал сопутствующие строительные лесаquanyj-react-cli, Ваша звезда - движущая сила для меня, чтобы продолжать двигаться вперед!
Введение в проект
-
В проекте используется Webpack5 + Typescript + React
-
представлен в проекте
core.scss
, Глобальное общедоступное, прямое использование не требует @import каждого файла scss -
Стили CSS3 автоматически совместимы при создании проекта, поэтому вам не нужно самостоятельно писать стили, совместимые с браузером.
-
Проект поддерживает маршрутизацию конфигурации
-
интегрирован в проект
connected-react-router
, маршрут хранится в сторе, а интерфейс берется напрямую из стора -
В проекте по умолчанию используется immer вместо immutable
-
Некоторые общие функции инструментов настроены в проекте по умолчанию.
-
проект для
axios
вторичная упаковка -
Проект может использовать px напрямую
-
В проекте используется множество декораторов, таких как @connect, @context и т.д. для упрощения кода
-
В проекте используются ESLint и Prettier для стандартизации кода.
-
Проект представляет husky + lint-staged для проверки коммитов git.
часть фрагмента кода
Страница Используйте корпус
Декоратор Хранить данные из соединения приобретают
Используйте декоратор withAppContextDecorators для получения данных от поставщика содержимого AppContext.
import React from "react";
import Page from "@/components/Page";
import { UPDATE_USER_ID } from "@/store/actions/user";
import connect from "@/store/connect";
import CPng from "@/assets//images/02.png";
import HomeChild from "./HomeChild";
import { withAppContextDecorators } from '@/common/AppContext';
import "./index.scss";
import { IAppContext } from '@/types/IContext';
interface IHomeProps {
userId: number;
updateId: (id: number) => void;
}
interface IHomeState {
count: number
}
const mapStateToProps = {
userId: "user.userId"
};
const mapDispatchToProps = {
updateId: UPDATE_USER_ID
};
@withAppContextDecorators
@connect(mapStateToProps, mapDispatchToProps)
class Home extends Page<IHomeProps & IAppContext, IHomeState>{
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount = () => {
setTimeout(() => {
this.props.updateId(5);
}, 3000)
console.log(this.props.test());
this.setState({
count: 1
})
}
render() {
const { userId } = this.props;
const { count } = this.state;
console.log("this.props==", this.props);
return (
<div className='home'>
<div className="text1">Hello, World!{userId} </div>
<div className="bg1">{count}</div>
<img className="img1" src={CPng} ></img>
<HomeChild></HomeChild>
</div>
);
}
}
export default Home;
конфигурация маршрутизации
Настраиваемая маршрутизация, отложенная загрузка и субподряд
import React from 'react';
export default {
routes: [
{
exact: true,
path: '/',
isDynamic: true,
component: React.lazy(() =>
import(/* webpackChunkName: "home",webpackPrefetch: true */ '@/pages/Home'),
),
},
{
exact: true,
path: '/page1',
isDynamic: true,
component: React.lazy(() =>
import(/* webpackChunkName: "page1",webpackPrefetch: true */ '@/pages/Page1'),
),
},
],
}
Адаптивная упаковка компонентов
import React from "react";
import Resolutions from "@/common/Resolution";
interface IResolutionComProps {
WIDTH?: number;
}
export default class ResolutionCom extends React.Component<IResolutionComProps> {
componentDidMount() {
this.resize();
window.onresize = this.resize;
}
resize = () => {
//获取设计稿的尺寸
const design = Resolutions.getDesign();
const getHtmlFs = () => {
return parseFloat(window.getComputedStyle(html, null)["font-size"]);
};
const getScreenWidth = () => {
let htmlWidth = 0;
try {
let htmlElement = document.documentElement;
htmlWidth = Math.max(htmlElement.offsetWidth || 0, htmlElement.clientWidth || 0, htmlElement.getBoundingClientRect().width || 0);
// 读取失败,其他方式读取
if (!htmlWidth || htmlWidth <= 0) {
if (window.orientation == 180 || window.orientation == 0) {
//竖屏
htmlWidth = window.innerWidth || (window.screen && window.screen.width) || (window.screen && window.screen.availWidth) || 0;
} else if (window.orientation == 90 || window.orientation == -90) {
//横屏
htmlWidth = window.innerHeight || (window.screen && window.screen.height) || (window.screen && window.screen.availHeight) || 0;
}
}
} catch (e) {
console.log("获取屏幕宽度出错");
}
return htmlWidth | 0;
}
let html = document.documentElement,
WIDTH = this.props.WIDTH || design.WIDTH,
//第一次进来没有设置过html标签font-size的时候
screenWidth = getScreenWidth(),
htmlFs = getHtmlFs(),
mediaFs = (design.RATIO / WIDTH) * screenWidth; //获取页面宽度 设备宽度/fontSize=设计稿(750)/100=7.5;
html.style.fontSize = mediaFs + "px"; //根据页面大小算出font-size
//以下是特殊处理 试过一台htc下的某个浏览器设置字体大小后再获取font-size会比所设的值会相对变小 所以设置大一点让它font-size的结果是想设的结果
if (htmlFs !== mediaFs && Math.abs(htmlFs - mediaFs) > 2) {
html.style.fontSize = "100px";
html.style.fontSize = (100 / getHtmlFs()) * mediaFs + "px";
}
};
render() {
return this.props.children;
}
}
connect-react-router Измените исходный код для поддержки использования immer
import { createHashHistory } from 'history';
import produce from "immer";
import { LOCATION_CHANGE } from 'connected-react-router';
import { IActionParam } from "@/types/IRedux";
let history = createHashHistory();
export default history;
//import { push } from 'connected-react-router';
//提供了push,go,goBack,replace,block,goForward方法。
//push("/home") || push({pathname:"/home",search:"name=1",hash:"1"})
//history 可以分为两部分,切换和修改,切换历史状态:back,forward,go对应浏览器的后退,跳转,前进。history.go(2);//前进两次
//push 把页面状态保存在state对象中,当页面回来的时候,可以通过event.state获取到state对象。
//查看connected-react-router 的connectRouter 方法,使immer与history 结合使用
export interface HistoryState {
location: any,
action: any,
}
const injectQuery = (location) => {
if (location && location.query) {
// Don't inject query if it already exists in history
return location
}
const searchQuery = location && location.search
if (typeof searchQuery !== 'string' || searchQuery.length === 0) {
return {
...location,
query: {}
}
}
// Ignore the `?` part of the search string e.g. ?username=codejockie
const search = searchQuery.substring(1)
// Split the query string on `&` e.g. ?username=codejockie&name=Kennedy
const queries = search.split('&')
// Contruct query
const query = queries.reduce((acc, currentQuery) => {
// Split on `=`, to get key and value
const [queryKey, queryValue] = currentQuery.split('=')
return {
...acc,
[queryKey]: queryValue
}
}, {})
return {
...location,
query
}
}
const initHistoryState: HistoryState = {
location: injectQuery(history.location),
action: history.action,
};
/* eslint-disable no-param-reassign */
export const reducer = produce((draft: HistoryState, actionParam: IActionParam) => {
if (actionParam.type === LOCATION_CHANGE) {
const { location, action, isFirstRendering } = actionParam.payload;
draft.action = action;
draft.location = injectQuery(location);
return draft;
}
return draft;
}, initHistoryState);
послесловие
Ваши комментарии приветствуются. Шаблон проекта постоянно оптимизируется, пожалуйста, лайкните один раз! Комментарии приветствуются.