React… 一天之内!





5.00/5 (3投票s)
在 Visual Studio 2017 中设置 React 项目。
引言
我经常在工作要求中看到“React”这个词,而且我一直在用Angular开发,我一直好奇这两个单页面应用框架究竟有多大区别。好吧,我想我等不及了,因为在一次面试中,他们让我开发一个简单的React页面。这让我措手不及,但在面试中我们永远不应该说“我不会”。
首先要提到的是项目定义,它大致是这样的:使用React创建一个简单的单页面应用程序,其中包含一个汽车零部件录入表单。
- 汽车零部件录入表单必须包含以下字段:
- ID(唯一标识符,可自动生成)
- 零件号(随机5位数字,以
00001
开头) - 零件名称(汽车零件的名称)
- 描述(描述汽车零件)
- 汽车制造商名称(克莱斯勒、道奇、福特等)
- 应用程序必须能够将汽车零部件录入保存到数据库
- 应用程序必须能够编辑汽车零部件录入
- 应用程序必须能够删除汽车零部件录入
为了完成这个项目,我决定使用Visual Studio 2017,并考虑到这些需求来探索React框架。幸运的是,互联网为任何主题提供了无数的资源。所以,我按步骤进行如下:
第一步:创建React项目
这是任何.NET开发人员都非常熟悉的步骤。但过程是这样的:
- 打开Visual Studio 2017并选择创建新项目。
- 在弹出的窗口中,选择项目类型,本例中是ASP.NET Core Web Application。这是.NET Core模板组的一部分。
- 选择项目的文件夹位置,分配项目名称(React Demo)和解决方案名称(React Demo)。
- 勾选“为解决方案创建目录”选项。
- 如果您有GIT账户并希望将此项目持久化到GIT中,请也勾选该选项。
- 单击“确定”后,下一个窗口显示不同的项目模板。选择ReactJS模板并单击“确定”。
- 第一个窗口应该像这样。第二个窗口非常直接,所以我没有在这里显示它,但它需要选择ReactJS选项并单击“确定”。
解决方案资源管理器将如下所示:
运行项目时,我们会得到这个:
太棒了!一个错误。如果我们选择项目名称并单击“显示所有文件”,我们会注意到没有node_modules文件夹。所以我们的第一步是打开项目文件级别的命令提示符并运行NPM Install。第二次运行项目……Bingo!
这是一个项目的好起点,其中包含一些我们可能不需要的额外东西。在我们的例子中,我们不需要Counter和Fetch Data菜单项以及相关的页面。我们需要Home,但可能需要更改其文本。为了提交一个React Demo,我决定将Home页面的文本替换为项目需求。结果将是这样的:
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
export class Home extends React.Component<RouteComponentProps<{}>, {}> {
public render() {
return <div>
<h1>React Demo</h1>
<p><u>Tasks:</u></p>
<p>Make a simple one-page application with a car part entry form.</p>
<p>Car part entry form must have the following fields:</p>
<ul>
<li>ID (unique identifier, can be automatically generated).</li>
<li>Part Number (random 5-digit number starting with 00001.</li>
<li>Part name (name of car part. </li>
<li>Description (describe the car part).</li>
<li>Car Manufacturer Name (Chrysler, Dodge, Ford, etc.)</li>
</ul>
<p>App must be able to save the car part entries to a database</p>
<p>App must be able to edit car part entries</p>
<p>App must be able to remove car part entries</p>
</div>;
}
}
另外,我决定删除Counter和Fetch Data,并添加React Demo。第一步很容易,只需打开Home.tsx文件并替换文本。第二步需要您打开NavMenu.tsx文件。在该文件中,删除这些行:
<li>
<NavLink to={ '/counter' }
activeClassName='active'>
<span className='glyphicon
glyphicon-education'></span> Counter
</NavLink>
</li>
<li>
<NavLink to={ '/fetchdata' }
activeClassName='active'>
<span className='glyphicon
glyphicon-th-list'></span> Fetch data
</NavLink>
</li>
并用这些行替换:
<li>
<NavLink to={ '/reactdemo' }
activeClassName='active'>
<span className='glyphicon
glyphicon-education'></span> React Demo
</NavLink>
</li>
同样,在routes.tsx文件中,删除counter和fetchdata路由,并像这样用“carpart
”路由替换它们:
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';;
import { CarPart } from './components/ CarPart;
export const routes = <Layout>
<Route exact path='/' component={ Home } />
<Route path='/ carpart component={ CarPart } />
</Layout>;
我们可以删除Counter.tsx和FetchData.tsx,并添加一个新的carPart.tsx。为了添加carPart tsx文件,我只是添加了一个typescript文件并将其扩展名更改为tsx。下面的代码显示了CarPart
组件的详细信息:
此组件有两个事件方法HandleSave
和HandleDelete
,在单击相应的按钮时触发。我们要在这些方法中观察的是React的Fetch
调用。对于HandleSave
,它包括标头数据,并将对象作为JSON对象的一部分传递到标头中,这样,在控制器中,SavePartData
方法中类型为CarPartModel
的参数carPart
将被[FromModel]
修饰,这意味着不需要映射任何内容即可传递对象实例。
对于HandleDelete
,唯一需要的参数是要从数据库中删除的零件ID。
import 'bootstrap/dist/css/bootstrap.min.css';
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import Link, { LinkedComponent } from 'valuelink';
interface CarPartModel {
ID: number;
PartNumber: number;
PartName: string;
Description: string;
CarManufacturer: string;
}
const carManufacturers = [
{ label: "Ford", value: 1 },
{ label: "Chevrolett", value: 2 },
{ label: "Tesla", value: 3 },
{ label: "Chrysler", value: 4 },
{ label: "Honda", value: 5 },
{ label: "Toyota", value: 6 },
];
export class CarPart extends React.Component<RouteComponentProps<{}>, CarPartModel> {
state: CarPartModel = {
ID: 0,
PartNumber: 0,
PartName: '',
Description: '',
CarManufacturer: ''
};
constructor() {
super();
this.state = {
ID: 0,
PartNumber: 0,
PartName: '',
Description: '',
CarManufacturer: ''
};
this.handleSave = this.handleSave.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
handleSave = () => {
fetch('api/CarParts/SavePartData', {
method: 'post',
headers: new Headers({
"Content-Type": "application/json",
Accept: "application/json"
}),
body: JSON.stringify(this.state)
}).then(function (response) {
if (response.status !== 200) {
console.log('fetch returned not ok' + response.status);
}
}).catch(function (err) {
console.log(`error: ${err}`);
})
}
handleDelete = () => {
if (!confirm("Do you want to delete employee with Id: " + this.state.ID))
return;
else {
fetch('api/CarParts/Delete/' + this.state.ID, {
method: 'delete'
}).then(data => {
this.state =
{
ID: 0,
PartNumber: 0,
PartName: '',
Description: '',
CarManufacturer: ''
};
});
}
}
public handleIdChange(event: any): void {
this.state.ID = event.target.value;
}
public handlePartNumberChange(event: any): void {
this.state.PartNumber = event.target.value;
}
public handlePartNameChange(event: any): void {
this.state.PartName = event.target.value;
}
public handleDescriptionChange(event: any): void {
this.state.Description = event.target.value;
}
public handleManufacturerChange(event: any): void {
this.state.CarManufacturer = event.target.value;
}
public render() {
return <div>
<h1>Car Parts</h1>
<div className='row'>
<div className='col-md-2'>
<label>Part Id: </label>
</div>
<div className='col-md-2'>
<input className="form-control" type="text"
name="partId"
onChange={e => this.handleIdChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Part Number: </label>
</div>
<div className='col-md-2'>
<input className="form-control"
type="text " name="partNumber"
onChange={e => this.handlePartNumberChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Part Name: </label>
</div>
<div className='col-md-2'>
<input className="form-control"
type="text" name="partName"
onChange={e => this.handlePartNameChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Description: </label>
</div>
<div className='col-md-2'>
<input className="form-control"
type="text" name="description"
onChange={e => this.handleDescriptionChange(e)} required />
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row'>
<div className='col-md-2'>
<label>Car Manufacturer:</label>
</div>
<div className='col-md-2'>
<select onChange={e => this.handleManufacturerChange(e)}>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="chrysler">Chrysler
</option><option value="saab">Saab</option>
<option value="dodge">Dodge</option>
<option value="ford">Ford</option>
</select>
</div>
<div className='col-md-8'>
</div>
</div>
<div className='row mt-3'> </div>
<div className='row'>
<div className='col-md-1'>
<button onClick={this.handleSave} className='btn
btn-info btn-lg'><span className="glyphicon glyphicon-ok"></span>
Save
</button>
</div>
<div className='col-md-1'>
<button onClick={this.handleDelete}
className='btn btn-info btn-lg'>
<span className="glyphicon glyphicon-remove"></span>
Delete
</button>
</div>
<div className='col-md-9'>
</div>
</div>
</div>
;
}
}
我添加的最后一项是项目服务。为此,我添加了一个services文件夹,并添加了一个名为CarPartService.cs和ICarPartService.cs的类。service类包含C#中的实际代码,它将与数据访问层(DAL)交互以执行CRUD操作。我没有在演示中包含它,因为它超出了项目目的的范围,该目的是展示使用React代码的能力。但为了测试目的,请在服务方法中设置断点并运行应用程序进行调试。
ICarPartInterface.cs的代码如下:
using ReactSample.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReactSample.Services
{
public interface ICarPartService
{
CarPartViewModel GetCartPart(int carPartId);
bool SaveCartPart(CarPartViewModel carPart);
bool UpdateCarPart(CarPartViewModel carPart);
bool Delete(int carPartId);
}
}
CarPartService.cs的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ReactSample.ViewModels;
namespace ReactSample.Services
{
public class CarPartsService : ICarPartService
{
public bool Delete(int carPartId)
{
try
{
// Find the car part associated with the given Id and delete it
return true;
}
catch(Exception ex)
{
// Log error message in case of error
}
return false;
}
public CarPartViewModel GetCartPart(int carPartId)
{
var carPart = new CarPartViewModel();
try
{
// Find the car part associated with the given Id,
// populate carPart object and return it
return carPart;
}
catch (Exception ex)
{
// Log error message in case of error
}
return null;
}
/// <summary>
/// Receives car part object and query database
/// if record exists then updates
/// if record does not exist then inserts
/// </summary>
/// <param name="carPart"></param>
/// <returns></returns>
public bool SaveCartPart(CarPartViewModel carPart)
{
try
{
// Save the car part object into database
return true;
}
catch (Exception ex)
{
// Log error message in case of error
}
return false;
}
public bool UpdateCarPart(CarPartViewModel carPart)
{
try
{
// Find the car part associated with the given Id
// and update fields using parameter object
return true;
}
catch (Exception ex)
{
// Log error message in case of error
}
return false;
}
}
}
为了使用服务层,我决定将其注入到控制器中。所以我创建了一个CarPartsController
在Controllers文件夹中,并添加了以下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ReactSample.Services;
using ReactSample.ViewModels;
namespace ReactSample.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class CarPartsController : Controller
{
ICarPartService _carPartService;
private readonly string[] CarManufacaturers = new[]
{
"Chryssler", "Dodge", "Ford",
"Jeep", "Chevrolett", "Honda",
"Toyota", "Subaru", "Nisan", "Kia"
};
public CarPartsController(
ICarPartService carPartService
)
{
_carPartService = carPartService;
}
[HttpGet("{id}", Name = "Get")]
public CarPartViewModel Get(int id)
{
return _carPartService.GetCartPart(id);
}
[HttpPost("SavePartData")]
public void SavePartData([FromBody]CarPartViewModel carPart)
{
_carPartService.SaveCartPart(carPart);
}
// PUT: api/Sample/5
[HttpPut("{id}")]
public void Update(int id, [FromBody]CarPartViewModel carPart)
{
_carPartService.UpdateCarPart(carPart);
}
// DELETE: api/ApiWithActions/5
[HttpDelete("Delete/{id}")]
public void Delete(int id)
{
_carPartService.Delete(id);
}
}
}
您会注意到,在演示中,我在此控制器中使用了两个方法SavePartData
和Delete
。我保留了其他方法,以防我决定为这个项目添加更多功能……纯属娱乐。如果您注意到此控制器构造函数有carPartService
参数,它被赋给了_carPartService
变量。为了使此工作正常,我们需要在Startup.cs文件的ConfigureServices
方法中添加以下几行,该方法最终将如下所示:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICarPartService, CarPartsService>();
services.AddMvc();
}
添加bootstrap并运行解决方案后,我们得到这个:
祝您编码愉快!