好久没有写博客, 有空再来记一下. 最近在整些小东西, 需要用到前端, 最开始本着对 Node.JS 的动不动几百兆插件的恐惧, 于是使用自己以前写的 OSS.Pjax 小框架(类似国外的 Pjax, 利用 pushState 达到单页的效果), 表单配合 jQuery 凑合一下, 有兴趣的可以看下我 GitHub 上的 OSS.Core.AdminUI . 不过本着学习精神, 还是额外挑战了下 React(主要是冲着 AntDesignPro 去的, 公司团队已经在使用), 结果这么稍微深入了解一下, 还真没能逃掉真香定律. 这篇文章主要借我个人经验, 梳理前后学习过程涉及知识点, 希望可以帮助有需要的同学, 特别是一直奋战在服务端但希望了解前端的同学.
1. 基础相关涉及知识
2. React 及相关扩展框架认识
3. AntDesign 相关开发框架
4. Umi 下 AntDesignPro 项目配置
5. .NetCore 项目下集成调试
在开始下边之前, 先贴一个涉及相关框架的图:
一. 基础相关涉及知识
碰上新框架, 不少同学的学习路径直接就从入门到放弃了. 这里让我们首先在战略上轻视它, 毕竟只是一个交互界面实现工具, 万变不离其宗, 还是要落地到 CSS,html,JS 这些基础实现上. 重点在于, 这个工具在这些基础实现之上做了哪些改进, 为了这些改进引入了哪些新的支撑. 首先基础层面的认知必须清晰.
这里我们先关注下 JS 相关概念, 有些名称 (如: ES5, ES6, ES2016) 可能上来就把很多人整蒙了. 这些和 JS 有何关联, 我们先来梳理一下. JS 最早随网景浏览器发布, 后来交由一个 ECMA 的组织制定统一标准, 其他的浏览器依此提供 JS 支持. ES5,ES6 就是这些标准的版本, 其定义了 JS 的相关语法标准规范. 粗暴点理解, 一个标准规范, 一个规范的具体实现. 特别是后边的 TypeScript, 在语法上自然是实现了相关标准, 于此同时它又引入了类, 接口, 泛型等其他高级语言特性, 特别是类型检查特性等, 使得代码更加可控和模块化, 所以它又称为 JS 的超集, 因为浏览器支持的是 JS 规范, 所以 TypeScript 最终编译成 JS 代码运行.
如果你还没有使用过 TypeScript, 不必急于学习, 后续继续使用 JS 也是 OK 的. 如果你想去学习了解, 也挺简单, 不容易理解的可能集中在部分复杂的类型处理, 这块和静态语言最大的不同就是类型结构是可以动态创建的, 也可以了解下它本身提供的 Pick,Exclude,Omit 几个高级类型的使用.
前端历史悠久, 除了 JS, 还有 CSS,HTML 也有了长足发展, 以至于横跨不同浏览器, 上下不同版本, 能够做到的支持各不相同. 这么多新特性使用起来给开发人员带来相当的压力, 开发过程中畏手畏脚, 多少同学为了兼容而身心疲惫. 还好有那么一群好心人做了各种兼容处理类库, 这些包对新特性, 新语法进行包装或转换, 使得最终的代码能够在低版本的浏览器中运行. 比如 Babel, 通过编译, 生成新的低版本代码. 当项目中使用了很多类库之后, 相互间的引用依赖, 编译顺序, 生成代码压缩等又成为新的问题, 于是我们又引入了 webpact 这类用来打包的工具类库, 通过各种配置用来处理打包压缩等处理.
有了这些工具类库, 给了我们能够使用新特性的机会, 又尽可能做到向下的兼容支持. 作为类库必须能够广泛传播复用, 肯定需要一个统一可靠的管理平台, 这时就引入了 Node.JS 的 NPM 包管理器, Node.JS 本身不过多说明, 我们先了解它就是一个能够让 JS 脱离浏览器直接运行的环境, NPM 就是在集成在这个环境中包的上传下载管理等命令引擎, 类似. Net 的 Nuget,Java 的 Maven. 有了这个管理器, 我们可以在项目中方便的通过 NPM 命令安装或运行对应的类库包.
二. React 及相关扩展框架认识
1. React
具体语法等请参见官方文档, 我们先认识其重要的核心组成部分.
首先, 我们要建立一个基础认知, React 也只是一个 JS 类库, 个人理解其核心包含两大块:
1.1 语法层面
通过对 JS 进行扩展的 JSX 语法, 完成对 UI 交互的包装渲染, 类似 Babel, 这些语法最终通过解释器转换成浏览器可执行的基础代码.
有了 JSX 语法, 我们可以像处理模板一样, 完成变量等和 HTML 元素的交互(如果不好理解, 就看成一段 HTML, 将变动的地方打上个标记, 调用时进行替换, 最终不同变量生成的 HTML 不同), 当然 JSX 并不是一个模板引擎, 它是通过语法糖的形式达到了这样的效果, 通过这个语法编写的代码呈现形式上又类似 HTML 元素. 虽然类似 HTML 元素, 但又有了动态的变化能力, 我们可以对这些代码其抽象成可复用的模块, 也就是组件, 这些组件通过暴露的属性接收外界的变化, 也就是 props 的这个东西, 组件就通过 props 获知个属性的变化值, 完成和组件外部的互动.
在这里特别说明一下, 针对 React 的组件, 里面又分了有状态 (class 定义) 和无状态 (function 定义) 组件, 我们先简单了解就好, 后续你会发现通过 React Hooks 的引入使用无状态组件基本都可以解决, 重要的是我们需要理解状态 (State) 这个东西
1.2 是围绕 State 的作用范围和生命周期.
有了元素组件, 也就有了可以通过控制参数来完成对 dom 元素操作完成页面变化的能力, 但如何和数据进行连接, 这时我们就引入状态的概念, 也就是 State 对象. 一旦 State 状态对象发生变化(setState 方法触发), 对应组件就会进行重新渲染, 以完成数据对页面的驱动. 当然在这个渲染过程中, React 内部进行了很多优化, 比如通过虚拟 dom 树的对比, 只有真正变化的元素才会重新渲染, 尽可能的提升和保证性能.
这里需要说明 React 和 vue,Angular 的一个重要不同: State 的数据和元素的绑定是单向的, State 的变化会驱动页面元素变化, 但页面输入框等的值变化并不会直接影响到 State. 单向还是双向各有优劣, React 的作者们已经做出了选择, 我们不必纠结.
因为 State 是一个状态对象, 就会存在一个作用的上下文范围和生命周期管理, 否则相互覆盖或者常驻内存就会引发各种未知问题.
a. 关于作用范围:
上边说了组件和外部的交互通过 Props 可以进行, 本着职责统一, State 就不需要参与外边的事情, 专职服务于组件内部即可. 这样组件内部数据管理在 State 中, 外部通过 Props 传入, 多层级嵌套引用时相互不会造成数据污染.
组件状态发生变化, 组件本身及相关联的子组件接收到变化完成变动, 这种层层的嵌套, 再加上 State 在数据绑定的单向特性, 数据就像一个金子塔一样由塔尖传递到塔底. 所谓的 React 数据在组件层面是向下流动的就容易理解了.(当然父组件可以传递方法给子组件, 子组件事件执行时触发对父级组件的回调)
b. 关于生命周期
了解了状态的作用范围, 我们就可以很好理解它的生命周期了, 因为它仅需服务组件本身, 随着组件渲染卸载一起生存即可.
以此为依据我们可以很轻易的理解一下两个流程
组件新建生命过程: 1. 初始化构造组件 =》 2. 获取父组件传入的 Props =》3. 初始化状态对象 =》 4. 准备渲染 =》 5. 实际渲染呈现 =》6. 渲染页面完成
组件更新生命过程: 1. 接收父组件传入的 Props =》2. 组件更新判断(如果需要继续) =》 3. 准备更新 =》 4. 实际渲染呈现 =》 5. 更新组件完成
更新过程中如果状态变化来自自身, 跳过第一步. 除了上边的两个流程, 还有一个独立的事件, 就是组件无需展示时卸载事件. 这里不具体列出对应过程的方法名称, 没有意义, 我们主要了解处理的过程. 特别是在后续的真正使用时, 我们不会有太多机会需要直接操作相关事件.
一个简单的组件定义示例:
- function Welcome(props) {
- return <h1>Hello, {props.name}</h1>;
- }
学习地址: https://react.docschina.org/docs/getting-started.html
2. React Hooks
原本 React 本身已经提供了语法和状态管理机制, 但是这些本身相对基础, 特别是生命周期的变化配合状态的变化. 比如首次 组件加载完成后 通过 Ajax 加载了部分数据, 父级组件变化, 导致当前组件更新, 但是调用的是 更新组件方法, 那就需要在这两个方法里都去实现一遍 Ajax 请求. ReactHook 的引入, 对 React 本身基础部分进行再次包装. 用最简单的语句, 完成原本复杂的实现. 让我们可以在很大程度上不在需要关心生命周期的具体事件定义, 不需要关心类组件的状态初始化等处理.
在最新的 React 版本中已经包含了 Hooks 相关实现, 说两个重要的 Hook 实现:
2.1 State Hook
这个主要就是对状态的包装, 原本需要声明 Class 组件, 构造函数中声明 State 对象才行, 历史写法我们可以不再关心, 当前实现示例如下:
- import React, { useState } from 'react';
- function Example() {
- // 声明一个叫 "count" 的 state 变量
- const [count, setCount] = useState(0);
- return (
- <div>
- <p>You clicked {count} times</p>
- <button onClick={() => setCount(count + 1)}>
- Click me
- </button>
- </div>
- );
- }
- 2.2 Effect Hook
这个主要是对声明周期事件的包装, 上边我已经列出生命周期中几个过程, 其中我们开发过程中经常可能涉及数据加载处理集中在: 初始渲染加载完成, 组件更新渲染完成, 组件卸载 这三步中, EffectHook 主要就是对这三者的组合封装.
历史写法我们无需关心, 最新写法如下:
- import React, { useState, useEffect } from 'react';
- function Example() {
- const [count, setCount] = useState(0);
- // 数据变化时, 自动修改页面 title
- // 初始化和更新完成时, 会自动调用 useEffect 传入的方法 FuncA
- // 如果传入的方法内部又返回了一个方法 FuncB, 则 FuncB 会在组件卸载时执行
- useEffect(() => {
- document.title = `You clicked ${count} times`;
- });
- return (
- <div>
- <p>You clicked {count} times</p>
- <button onClick={() => setCount(count + 1)}>
- Click me
- </button>
- </div>
- );
- }
学习地址: https://react.docschina.org/docs/hooks-intro.html
3. ahooks
这个框架以前叫 Umi Hooks, 现在叫 AHooks(应该是定位为独立的通用框架), 是阿里团队出的, 主要是在 React Hooks 之上的扩展, 包含了界面处理, 到网络请求等各种常用操作, 比如我们想实时获取页面滚动信息, 示例如下:
- import React from 'react';
- import { useScroll } from 'ahooks';
- export default () => {
- const scroll = useScroll(document);
- return (
- <div>
- <div>{JSON.stringify(scroll)}</div>
- </div>
- );
- };
学习地址: https://ahooks.js.org/
4. React Router
React 的路由类库, 前面我们已经有了相关组件的定义, 还局限在页面内. 一个站点 Url 地址肯定不止一个, 访问不同的页面 URL 地址, 如何加载不同的组件 , 就交由这个类库来处理.
React Router 的职责就是拿到页面浏览器地址, 完成对应的组件加载, 或者是组件替换时, 反向修改当前页面浏览器地址. 如果有兴趣可以深入研究, 否则了解大概用法即可, 相对简单, 示例如下:
- React.render((
- <Router>
- <Route path="/" component={App}>
- <IndexRoute component={Dashboard} />
- <Route path="about" component={About} />
- </Route>
- </Router>
- ), document.body)
5. 其他类库
这里简单列几个其他 React 扩展类库, 这些当时浪费了我一些时间, 其实完全可以跳过, 这些基本出现在 React Hook 出现之前, 为了完成一些复杂页面处理.
Redux, 这个主要是用来管理状态, 用来解决相关变化和异步的问题. 试想一个 State, 涉及多个异步加载组装, 以及相关组装过程中的数据再加工, 还是挺复杂的.
Redux-Saga, 主要是在 Redux 之上的一个异步相关封装, 无它, 就是搞得更复杂了.
DvaJS , 这个是阿里 ant 团队出品, 还是在 Redux 扩展的, 没啥意义了
了解以上, 基本上对 React 的轮廓有了一个大概的认知, 具体语法使用时翻阅文档即可. 下一步我们就可以进入到业务开发环节, 在实际的业务开发中我们都会遇到很多有共性且重复的组合模块, 在 jQuery 时代我们有 Bootstrap, 接下来我们介绍下在 React 下阿里的 AntDesign 相关框架.
三. Ant Design 相关开发框架
1. AntDesign(https://ant.design/)
这个是阿里团队在 React 基础上, 封装的业务开发常用 UI 组件类库列表, 包括按钮, 布局, 导航等等, 具体组件请参阅官方文档. 这些仅仅是一个个独立的组件, 不涉及具体的页面.
2. AntDesignPro(https://pro.ant.design/)
这个框架是 AntDesign 的升级, 可以理解为就是一个后台模板框架, 通过对 AntDesign 这些组件库的包装, 提供了统一样式, 基础布局, 列表页, 详情页.... 等这一类通用模板页面. 它内部又细分出几个独立模块, 如: 布局(ProLayout), 列表(ProTable)
3. UmiJS(https://umijs.org/)
这个框架可以说是我们后续开发的核心了, 这个框架包含两个部分
首先, 从开发工具上:
通过它, 我们可以直接创建 Ant Design Pro 应用项目, 其内部集成了 babel, webpact 等相关编译打包工具, 并已经进行了默认配置, 无需我们对下层过多关心, 除非你有相关特殊需求, 可以开启相关手动配置.
同时, 我们也可以通过编写模拟接口, 以及单元测试等.
其次在应用层面:
它内部集成了路由 (React Router) 等相关处理, 我们可以很方便的通过配置的方式书写路由和组件规则, 以及页面的跳转 (Link) 等处理.
同时也提供了部分应用插件 (集成了 ahooks ) 功能, 如: 网络请求, 国际化等
四. Umi 下 Ant Design Pro 项目配置
上边是整个相关框架轮廓和职责, 这一节我们主要开始真正开始通过 UmiJS, 进行 AntDesignPro 的开发.
首先, 我们需要安装 Node.JS 环境, 因为 NPM 包管理连接的外网, 我们添加国内的包源, 并使用 cnpm 命令代替:
- # 国内源
- NPM i -g cnpm --registry=https://registry.npm.taobao.org
在指定的文件夹中创建项目命令:
cnpm create umi
之后会让你选择项目类型, 我们选择 ant-design-pro 类型即可(同时会让你选择开发语言, 如果不熟悉 TypeScript, 可以选择 JS).
创建项目之后, 基本就能看到项目组成了, 很简单的结构. 之后我们只需要执行即可运行查看效果:
- cnpm install
- cnpm run start
这里我重点说下核心入口页面 src/App.ts 下的几个配置(所有相关配置基本都在 UmiJS 框架中, 可查阅相关文档):
1. 网络请求全局化配置, 如:
- export const request: RequestConfig = {
- headers: {'Content-Type': 'application/json',
- },
- errorConfig: {
- adaptor: (resData, { res }) => {
- if (res.status != 200 && !resData.errorMessage) {
- const errorMessage = codeMessage[res.status] || res.statusText;
- return {
- ...resData,
- errorMessage,
- };
- }
- return resData;
- },
- }
- };
- // 参考: https://umijs.org/zh-CN/plugins/plugin-request
2. getInitialState, 这个方法会在第一次初始化时进行执行, 如:
- // src/App.ts
- export async function getInitialState() {
- const data = await fetchXXX();
- return data;
- }
- // 参考: https://umijs.org/zh-CN/plugins/plugin-initial-state#getinitialstate
3. 配置页面布局, 如:
- // src/App.JS
- export const layout = {
- logout: () => {}, // do something
- rightRender:(initInfo)=> { return 'hahah'; },// return string || ReactNode;
- };
- // 参考: https://umijs.org/zh-CN/plugins/plugin-layout
五. Net Core 项目下集成调试
如果你直接通过 VS 创建. Net Core 的 React 项目, 你会发现在项目文件下, 会存在以下节点:
- <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition="'$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
- <!-- Ensure Node.js is installed -->
- <Exec Command="node --version" ContinueOnError="true">
- <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
- </Exec>
- <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
- <Message Importance="high" Text="Restoring dependencies using'npm'. This may take several minutes..." />
- <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
- </Target>
- <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
- <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
- <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
- <ItemGroup>
- <DistFiles Include="$(SpaRoot)build\**" />
- <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
- <RelativePath>%(DistFiles.Identity)</RelativePath>
- <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
- <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
- </ResolvedFileToPublish>
- </ItemGroup>
- </Target>
在 Startup.cs 文件下存在:
- public void ConfigureServices(IServiceCollection services)
- {
- // ... 其他代码
- services.AddSpaStaticFiles(configuration =>
- {
- configuration.RootPath = "ClientApp/build";
- });
- }
- public void Configure(IApplicationBuilder App, IWebHostEnvironment env)
- {
- // ... 其他代码
- App.UseSpa(spa =>
- {
- spa.Options.SourcePath = "ClientApp";
- if (env.IsDevelopment())
- {
- spa.UseReactDevelopmentServer(npmScript: "start");
- }
- });
- }
也就是每次生成时都会执行 (NPM install), 在调试的时候通过 Startup.cs 的代码执行(NPM start), 但是 NPM start 比较耗费时间, 每次调试都运行一次(甚至在页面无修改的情况下), 及其的不便利, 特别是 AntDesignPro 相关的包特别多, 极容易卡死.
这里我们只需要正常创建 ASP.NET Core 项目(不要选择 React), 前端部分放在. Net Core 项目文件夹之外, 在外部通过 VS code 开发, 需要调试时 , 直接通过. Net Core 中提供的代理的方式进行, 在 Startup 中的方法如下:
- public void ConfigureServices(IServiceCollection services)
- {
- // ... 其他代码
- services.AddSpaStaticFiles(configuration =>
- {
- configuration.RootPath = "ClientApp/build";
- });
- }
- public void Configure(IApplicationBuilder App, IWebHostEnvironment env)
- {
- // ... 其他代码
- App.UseSpa(spa =>
- {
- spa.Options.SourcePath = "ClientApp";
- if (env.IsDevelopment())
- {
- spa.UseProxyToSpaDevelopmentServer("http://localhost:8000");
- }
- });
- }
这样既保证了项目文件的干净, 又能够保证. net core 项目的快速调试运行, 在发布时, 只需要将 前端项目的文件发布到. Net Core 项目发布目录下的 "ClientApp/build" 文件夹中即可.
如果你已经看到这里, 并且感觉还行的话可以在下方点个赞, 或者也可以关注我的公总号(见二维码)
_________________________________________
来源: https://www.cnblogs.com/osscoder/p/13220670.html