实例概述
本文提供一个使用阿里Ice前端框架(封装React)与服务Spring Boot项目进行相互通信的项目实例。具体项目环境可参见:阿里ICE前端工程创建过程。该实例中不对Spring Boot项目的创建和使用进行介绍,仅提供相应的Controller方法作为与React前端工程通信的API。该实例具有的相关组件组成的页面如下:
- UserCreateForm:提供表单信息进行Post请求创建用户。
- UserListTable:用于获取数据库中的user列表并展示。
- UserDetailForm:用户渲染指定用户的详细信息并提供修改的功能。
以上三个组件的关系为并列(没有进行组合),但其中存在一些页面跳转的关系,后续会进行说明。
在该实例中重点进行关注和介绍的问题点为:
- React与Spring Boot前后端的Get/Post请求交互方式如何实现?
- ice中页面之间的跳转如何实现?并如何携带和获取参数?
- React中props和state的使用。
- React如何使用map渲染列表?
- 服务端跨域请求的的设置。
项目代码:https://github.com/Yitian-Zhang/my-ice-start。下面来看具体的实例实现过程。
服务端Controller和跨域问题设置
服务端方面已经使用Spring Boot+MyBatis+MySQL搭建好项目环境,下面创建ReactUserController类提供如下的接口方法,并通过测试各接口功能正常。
/**
* React(my-ice-start)项目接口Controller
* @author yitian
*/
@RestController
@RequestMapping("/react-user")
public class ReactUserController {
@Autowired
private UserService userService;
@RequestMapping("/detail")
public CommonResult getUserDetail(Long id) {
if (id == null) {
return CommonResult.error("用户ID不能为空");
}
return new CommonResult(true, 200, userService.getUserById(id));
}
@RequestMapping("/create")
public CommonResult createUser(@RequestBody User user) {
if (user == null) {
return CommonResult.error("添加用户为空");
}
System.out.println(user);
int result = userService.insertUser(user);
boolean success = result == 1;
String msgInfo = success ? "添加成功" : "添加失败";
return new CommonResult(success, msgInfo, user);
}
@RequestMapping("/list")
public CommonResult userList() {
List<User> userList = userService.getUserList();
return new CommonResult(true, "获取成功", userList);
}
@RequestMapping(value = "/update", method = RequestMethod.POST)
public CommonResult updateUser(@RequestBody User user) {
if (user == null || user.getId() == null) {
return CommonResult.error("待更新用户信息为空");
}
System.out.println(user);
int result = userService.updateUserById(user);
boolean success = result == 1;
String msg = success ? "更新成功" : "更新失败";
return new CommonResult(success, msg, userService.getUserById(user.getId()));
}
@RequestMapping("/delete")
public CommonResult deleteUser(Long id) {
if (id == null) {
return CommonResult.error("UserId不能为空");
}
int result = userService.deleteUser(id);
boolean success = result == 1;
String msg = success ? "删除成功" : "删除失败";
return new CommonResult(success, msg, userService.getUserList());
}
}
此外由于Spring Boot项目为localhost:8080,而ice启动项目地址为localhost:3333,所以在前后端项目进行通信时会存在跨域问题,下面在Spring Boot项目中加入如下的Bean,来配置请求的response返回头,使其允许所有的request:
/**
* 解决React跨域请求时的异常
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
至此该实例中的Spring Boot服务端项目的方法已经开发完成,后面重点关注ICE项目中如何进行前后端项目的交互。
创建并添加用户
首先进行createUser的页面开发,这里定义的组件如下:
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
import PropTypes from 'prop-types';
import axios from 'axios';
@withRouter
class UserCreateForm extends Component {
// 页面跳转静态属性
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
username: '',
sex: 'MALE',
note: ''
};
}
// 表单变更处理函数
handleChange = (event) => {
const {name, value} = event.target;
this.setState({
[name] : value,
})
};
// 创建用户信息
handleSubmit = (event) => {
this.submitUser();
};
// post请求提交更新后的user信息
submitUser() {
const {username, sex, note} = this.state;
const { history } = this.props;
console.log(username + ", " + sex + ", " + note);
// 直接使用post请求
axios.post('http://localhost:8080/react-user/create', {
// id: id,
userName: username,
sex: sex, // 这里可以直接根据SexEnum的枚举name来进行参数传递,不需要使用枚举key
note: note
})
.then(function (response) {
console.log(response);
alert(response.data.msgInfo);
// 添加完成后跳转list页面
history.push({
pathname: '/user/list',
});
})
.catch(function (error) {
console.log(error);
});
};
render() {
const {username, sex, note} = this.state;
return (
<React.Fragment>
<h1>User Detail Form</h1>
<form>
<table>
<tr>
<td>Username:</td>
<td><input
type="text"
id="username"
name="username"
value={username}
onChange={this.handleChange}/></td>
</tr>
<tr>
<td>sex:</td>
<td><select
name="sex"
value={sex}
onChange={this.handleChange}>
<option value="MALE">MALE</option>
<option value="FEMALE">FEMALE</option>
</select></td>
</tr>
<tr>
<td>note:</td>
<td><input
type="text"
id="note"
name="note"
value={note}
onChange={this.handleChange}/></td>
</tr>
<tr>
<td><input
type="button"
value="CreateUser"
onClick={this.handleSubmit}/></td>
</tr>
</table>
</form>
</React.Fragment>
)
}
}
export default UserCreateForm;
该部分代码中有如下几个方面需要重点关注:
1. 在submitUser方法中使用axios进行post请求。
ice允许使用自己封装的request进行请求的发送(其实就是封装的axios),也允许使用ajax、jquery,axios等方式发送请求。这里使用了axios的方式进行post的请求的发送。格式如下:
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
或者使用axios(config {...}) 的格式发送post请求:
// Send a POST request
axios({
method: 'post',
url: '/user/12345',
data: { // 这里data中的参数为requestBody参数,服务端需要使用@RequestBody注解进行获取
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
2. 用户创建成功后如何进行的页面跳转(跳转页面代码的实现在后面)。
对于页面的跳转过程的实现,这里使用的为阿里Ice中实现组件和页面间跳转并进行参数传递中提到的,withRouter方式进行实现。
页面实现完成后,显示如下,在后续跳转页面完成后,进行完整的集成测试。
用户列表和管理
用户列表渲染
上面在创建用户完成后,页面会跳转到显示数据库中所有用户的列表页面中,该列表页面使用如下的组件进行实现:
import React, {Component} from 'react';
import axios from 'axios';
import {List} from "@alifd/next";
import UserDetailForm from "../UserDetailForm";
import {withRouter} from "react-router-dom";
import PropTypes from 'prop-types';
import './index.css';
@withRouter
class UserListTable extends Component {
// 页面跳转静态配置
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
userList: [],
};
}
componentDidMount() {
this.getUserList();
}
// 获取用户列表数据
async getUserList() {
try {
const response = await axios.get('http://localhost:8080/react-user/list');
console.log(response);
this.setState({
userList: response.data.data,
})
} catch (error) {
console.error(error);
}
}
// 详情和更新页面
handleClickDetail = (id) => {
console.log("ListTable id: " + id);
// 页面跳转
const { history } = this.props;
history.push({
pathname: '/user/detail',
state: { id },
});
};
// 删除数据
handleClickDelete = (id) => {
this.deleteUser(id);
};
// 删除用户
async deleteUser(id) {
try {
const response = await axios.get('http://localhost:8080/react-user/delete?id=' + id);
console.log(response);
alert(response.data.msgInfo);
this.setState({
userList: response.data.data,
});
} catch (e) {
console.error(e);
}
}
render() {
const {userList} = this.state;
return (
<div>
<h1>User List</h1>
<table>
<thead>
<tr>
<td>Id</td>
<td>UserName</td>
<td>Sex</td>
<td>Note</td>
<td>Operate</td>
</tr>
</thead>
<tbody>
{
userList.map((row, index) => {
const id = row.id;
return (
<tr key={index}>
<td>{row.id}</td>
<td>{row.userName}</td>
<td>{row.sex}</td>
<td>{row.note}</td>
<td>
<button
className="listButton"
onClick={() => this.handleClickDetail(id)}>Detail</button>
<button
className="listButton"
onClick={() => this.handleClickDelete(id)}>Delete</button>
</td>
</tr>
)
})
}
</tbody>
</table>
</div>
);
}
}
export default UserListTable;
对于上述的代码,需要关注的重点如下:
1. 如何使用axios的GET请求来操作数据?
在使用axios进行GET请求时,与POST请求类似,有如下两种方式。第一种为直接使用封装的axios.get进行请求,格式如下:
// Make a request for a user with a given ID
axios.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
// update state or do something
this.setState({
// ...
})
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
// Optionally the request above could also be done as
axios.get('/user', {
params: { // 这里的参数设置为URL参数(根据URL携带参数)
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
第二种为使用axios(config {...}) 的方式发送GET请求:
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
params: {
id: id,
}
})
.then(function (response) {
console.log(response);
});
2. 如何在页面跳转时传递需要的参数?
在页面跳转时传递参数,需要在history.push方法中进行如下设置:
// 页面跳转
const { history } = this.props;
history.push({
pathname: '/user/detail',
state: { id },
});
之后在用户详情页面就可以通过如下的方式获取该传递的id参数:
componentDidMount() {
let id = this.props.location.state.id;
console.log("DetailForm id: " + id);
}
3. 如何根据userList渲染出用户的整个列表页面?
在对userList进行循环渲染整个user列表时,需要首先在constructor中对userList整个state进行初始化为数组:
constructor(props) {
super(props);
this.state = {
userList: [],
};
}
然后在render方法中使用map方法对数组进行遍历并渲染列表中的数据:
<tbody>
{
userList.map((row, index) => {
const id = row.id;
return (
<tr key={index}>
<td>{row.id}</td>
<td>{row.userName}</td>
<td>{row.sex}</td>
<td>{row.note}</td>
<td>
<button
className="listButton"
onClick={() => this.handleClickDetail(id)}>Detail</button>
<button
className="listButton"
onClick={() => this.handleClickDelete(id)}>Delete</button>
</td>
</tr>
)
})
}
</tbody>
完成后该页面显示如下 :
对于每个用户,其中都对应了detail和delete按钮。其中detail会跳转到UserDetail页面来显示该用户的具体信息,delete按钮则是对该用户信息进行删除。下面先看一下简单的delete方法的实现,detail的实现在下一节中进行说明。
用户删除实现
上述列表中delete用户时,同样是使用axios.get请求来删除数据库中的用户数据,然后获取新的userList返回值并使用setState方法更新state,使该页面重新渲染。具体的代码已经在上面组件代码中给出,实现比较简单。
用户详情信息以及修改
用户详情页为userList页面中对指定user点击detail按钮而跳转得到的页面,该页面组件的实现如下:
import React, {Component} from 'react';
import axios from 'axios';
import {request} from "../../../.ice";
import {withRouter} from 'react-router-dom';
import PropTypes from 'prop-types';
@withRouter
class UserDetailForm extends Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
id: '',
username: '',
sex: '',
note: ''
};
}
componentDidMount() {
// 使用axios进行get和post请求
let id = this.props.location.state.id;
console.log("DetailForm id: " + id);
this.getUserByAxios(id);
}
// 使用axios来进行get请求
// 使用前需要安装axios:npm install axios --save,并进行import
async getUserByAxios(id) {
try {
const response = await axios.get("http://localhost:8080/react-user/detail?id=" + id);
console.log(response);
const user = response.data.data;
this.setState({
id: user.id,
username: user.userName,
sex: user.sex,
note: user.note
})
} catch (error) {
console.error(error);
}
}
// 表单变更处理函数
handleChange = (event) => {
const {name, value} = event.target;
this.setState({
[name] : value,
})
};
// 更新用户信息函数
handleSubmit = (event) => {
this.submitUser();
};
// post请求提交更新后的user信息
submitUser() {
const {id, username, sex, note} = this.state;
console.log(id + ", " + username + ", " + sex + ", " + note);
axios.post('http://localhost:8080/react-user/update', {
id: id,
userName: username,
sex: sex,
note: note
})
.then(function (response) {
console.log(response);
alert(response.data.msgInfo);
// 更新列表state
const user = response.data.data;
this.setState({
id: user.id,
username: user.userName,
sex: sex,
note: note
});
})
.catch(function (error) {
console.log(error);
});
};
render() {
const {id, username, sex, note} = this.state;
return (
<React.Fragment>
<h1>User Detail Form</h1>
<form>
<table>
<tr>
<td>Id:</td>
<td><input
type="text"
id="id"
name="id"
value={id}
disabled="true"
onChange={this.handleChange}/></td>
</tr>
<tr>
<td>Username:</td>
<td><input
type="text"
id="username"
name="username"
value={username}
onChange={this.handleChange}/></td>
</tr>
<tr>
<td>sex:</td>
<td><select
name="sex"
value={sex}
onChange={this.handleChange}>
<option value="MALE">MALE</option>
<option value="FEMALE">FEMALE</option>
</select></td>
</tr>
<tr>
<td>note:</td>
<td><input
type="text"
id="note"
name="note"
value={note}
onChange={this.handleChange}/></td>
</tr>
<tr>
<td><input
type="button"
value="UpdateUser"
onClick={this.handleSubmit}/></td>
</tr>
</table>
</form>
</React.Fragment>
)
}
}
export default UserDetailForm;
该部分代码中同时用到了axios.get请求来根据页面跳转传入的id参数,获取该用户对应的详细信息,同时使用axios.post请求在处理对用户信息的更新操作。以此实现了用户详细信息页面的展示,以及用户详情信息的更新功能。
该页面的显示为:
Ice项目路由配置
上述代码中在进行页面跳转时,使用到了如下的请求路径:
/user/list
/user/detail
在ice项目中,需要在routes.j[t]s路由配置文件中来进行声明式路由的配置:
const routes = [
{
path: '/user/list',
component: UserListTable,
},
{
path: '/user/detail',
component: UserDetailForm,
},
...
];
export default routes;
以上即为该实例的全部实现。END。