React学习路径快速进入AntDesignPro开发
好久没有写博客,有空再来记一下。最近在整些小东西,需要用到前端,最开始本着对nodejs的动不动几百兆插件的恐惧, 于是使用自己以前写的 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这类用来打包的工具类库,通过各种配置用来处理打包压缩等处理。
有了这些工具类库,给了我们能够使用新特性的机会,又尽可能做到向下的兼容支持。作为类库必须能够广泛传播复用,肯定需要一个统一可靠的管理平台,这时就引入了Nodejs的npm包管理器,Nodejs本身不过多说明,我们先了解它就是一个能够让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>
);
};
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的开发。
首先,我们需要安装Nodejs环境,因为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”文件夹中即可。
如果你已经看到这里,并且感觉还行的话可以在下方点个赞,或者也可以关注我的公总号(见二维码)
_________________________________________