30分钟极速通关react mobx react-router及打通springboot

8次阅读

共计 9495 个字符,预计需要花费 24 分钟才能阅读完成。

内容导航

简单开发 react
将 react 与 mobx 结合开发
使用 react-router 进行多页面开发
将项目打包到后端项目中进行部署
将完成的项目做成脚手架,避免重复的环境搭建

需要环境

确保 node 已经安装
确保 npm 已经安装

创建项目
npx create-react-app test
# test 为你需要创建项目的名字,会在命令当前目录下创建 test 的目录,包含项目所有的文件
你已经完成了创建,开始跑起来
npm start
你可以看到 react 已经能够在 local host:3000 访问了,只有一个欢迎页面
目录结构

node_modules
是当前目录安装的模块存放的地方

public
index.html 是单页面的入口

src
可存放自己编写的代码,App 是默认生成的欢迎页面逻辑,index 是 js 的主入口

开始更改你的代码
A. react 简单开发
1. 将 App.js 的代码更改如下
import React, {Component} from ‘react’;
import ‘./App.css’;

class App extends Component {
constructor(props) {
super(props)
this.state = {todos: [{checked: false, text: “hello”}, {checked: true, text: “world”}]}
this.handleClick=this.handleClick.bind(this)
}

handleClick(index) {
let todos = this.state.todos
todos[index].checked = !todos[index].checked
this.setState({todos:todos})
}

render() {
let todos = this.state.todos
let todosDiv = todos.map((item, index) => {
return (<Todo index={index} checked={item.checked} text={item.text} handleClick={this.handleClick}/>)
})
return (
<div className=”App”>
{todosDiv}
</div>
);
}
}

class Todo extends Component {
constructor(props){
super(props)
this.handleClick=this.handleClick.bind(this)
}
handleClick() {
let index = this.props.index
this.props.handleClick(index)
};
render() {
return (
<p><input type={‘checkbox’} checked={this.props.checked} onClick={this.handleClick}/>
{this.props.text}:{this.props.index}
</p>
)

}
}

export default App;
再次 npm start 一下看看效果吧~

可以看到我们组件已经能够响应点击了
B. 引入 mobx 作为状态管理
提出问题
在上面我们可以看到想要更改状态是比较困难的,首先要将 handClick 方法由子组件传给父组件,再进行处理。如果我们的组件是
四五层组件的时候得一步一步的往上级传递,这就会导致组件传递写的很臃肿。这个时候就需要一个将状态(即 state 这个值)独立开来。
react 有很多状态管理的组件,比如 redux,mobx。但 redux 写起来还是不如 mobx 简单明了。下面我们就来接入 mobx。
接入步骤

安装依赖
npm install mobx –save
npm install mobx-react –save

启用装饰器语法
# 如果有 git 的话,要将没有保存的文件上传之后或者删除之后才能跑 eject 命令
yarn run eject
npm install –save-dev babel-preset-mobx
在 package.json 中找到 babel 项目, 在 presets 里面增加 ”mobx”
“babel”: {
“presets”: [
“react-app”,   
“mobx” 
]},

加入 core-decorators
npm install core-decorators –save

在 src 下增加 store.AppStore.js 文件
import {action, observable} from “mobx”;

class AppStore {
@observable todos;

constructor() {
this.todos = [{checked: false, text: “hello”}, {checked: true, text: “world”}]
}

@action.bound handleClick(index) {
let todos = this.todos
todos[index].checked = !todos[index].checked
}
}
export default AppStore;

改写 index.js
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
import App from ‘./App’;
import * as serviceWorker from ‘./serviceWorker’;
import {Provider} from “mobx-react”;
import AppStore from ‘./store/AppStore’

let rootStore = {}
rootStore[‘app’] = new AppStore()
ReactDOM.render(
<Provider {…rootStore}>
<App/>
</Provider>, document.getElementById(‘root’));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

改写 App.js
import React, {Component} from ‘react’;
import ‘./App.css’;
import {inject, observer} from “mobx-react”;
import {autobind} from “core-decorators”;
@inject(“app”)
@autobind
@observer
class App extends Component {
constructor(props) {
super(props)
}

render() {
let todos = this.props.app.todos
let todosDiv = todos.map((item, index) => {
return (<Todo index={index}/>)
})
return (
<div className=”App”>
{todosDiv}
</div>
);
}
}

@inject(“app”)
@autobind
@observer
class Todo extends Component {
constructor(props) {
super(props)
}

handleClick() {
let index = this.props.index
this.props.app.handleClick(index)
};

render() {
let index = this.props.index
let todo = this.props.app.todos[index]
return (
<p><input type={‘checkbox’} checked={todo.checked} onClick={this.handleClick}/>
{todo.text}:{index}
</p>
)

}
}

export default App;

“`
npm start 一下,来看看效果吧
简要说明

@inject(“app”) 表示注入在 index.js 中的 rootStore 的属性 app。是由 <Provider {…rootStore}> 这个标签来实现动态的注入的
@autobind 将组件之间的绑定自动完成
@observer mobx 用来将 react 组件转换为响应式组件的注解,详情查看 mobx 的文档
上面可以看出,将原本的 state 的属性抽离到 AppStore 中了,对值得更改方法也是直接调用 AppStore 的方法,从而避免了 react 组件的一级一级往上传递

C. 引入 react-router 作为多页面管理
提出问题
上面我们完成了单页面的开发。当需要多个页面时我们就需要使用 react-router 来对不同路径进行渲染了
接入 react-router 步骤

安装依赖
npm install react-router mobx-react-router –save

增加新的页面, 在 src 中增加 component/Test.js
import * as React from “react”;

class Test extends React.Component{
render() {
return(<p>welcome!</p>)
}
}
export default Test;

更改 index.js
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
import App from ‘./App’;
import * as serviceWorker from ‘./serviceWorker’;
import {Provider} from “mobx-react”;
import AppStore from ‘./store/AppStore’
import {Route, Router, Switch} from “react-router”;
import {RouterStore, syncHistoryWithStore} from “mobx-react-router”;
import createHashHistory from “history/createHashHistory”
import Test from “./component/Test”
let rootStore = {}
const hashHistory = createHashHistory()

const routerStore = new RouterStore()
const history = syncHistoryWithStore(hashHistory, routerStore)
rootStore[‘app’] = new AppStore()
routerStore[‘routing’] = routerStore

ReactDOM.render(
<Provider {…rootStore}>
<Router history={history}>
<p>here is the menu</p>
<Switch>
<Route path={“/test”} component={Test}/>
<Route path={“/”} component={App}/>
</Switch>

</Router>
</Provider>, document.getElementById(‘root’));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

npm start 一下,访问下 /#/test,和 /#/ 路径,看看效果吧

简要说明

createHashHistory 是单页面的访问,会在 url 加个 #号作为定位,这个对于要打包到后台作为页面时是很方便的。
如果你直接使用 node 部署的话可以直接使用 createBrowserHistory,url 就会是没有 #号的 url。

D. 结合 ui 框架
接入步骤

找到一个合适的 react ui 框架,install 之后按照 ui 框架的教程就可以开发一个相对比较好看的页面了
常见的框架有 semantic,bootstrap,ant 等。

E. 结合 maven 打包进 spring boot 项目
提出问题
当我们需要跟 spring boot 等后端项目结合,而又不想单独部署前端页面时,就需要打包进后端项目了
接入步骤

新建一个多模块的 maven 项目

按照之前创建的步骤,创建前端的模块,假设模块名字为 view,并在前端模块的目录下增加 pom.xml
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.2</version>
<executions>
&lt;– Install our node and npm version to run npm/node scripts–>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
&lt;– 指定 node 的版本例如 v6.9.1 –>
<nodeVersion>${nodeVersion}</nodeVersion>
<npmVersion>${npmVersion}</npmVersion>
<nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
<npmDownloadRoot>http://registry.npmjs.org/npm/-/</npmDownloadRoot>
</configuration>
</execution>

&lt;– Set NPM Registry –>
<execution>
<id>npm set registry</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
&lt;–<arguments>config set registry https://registry.npmjs.org</arguments>–>
<arguments>config set registry https://registry.npm.taobao.org</arguments>
</configuration>
</execution>

&lt;– Set SSL privilege –>
<execution>
<id>npm set non-strict ssl</id>
<goals>
<goal>npm</goal>
</goals>
&lt;– Optional configuration which provides for running any npm command –>
<configuration>
<arguments>config set strict-ssl false</arguments>
</configuration>
</execution>

&lt;– Install all project dependencies –>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
&lt;– optional: default phase is “generate-resources” –>
<phase>generate-resources</phase>
&lt;– Optional configuration which provides for running any npm command –>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>

&lt;– Build and minify static files –>
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

当进行 mvn package 时就会在目录下生成 build 目录,包含所有的页面和脚本了。

在 spring boot 后端项目中,将前端打包好的页面拷贝到后端目录中
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>Copy App Content</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/resources/public</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.parent.basedir}/view/build</directory>
<includes>
<include>static/</include>
<include>index.html</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

其中 outputDirectory 指明要放入的文件夹
directory 指明要拷贝哪里的资源文件,需要根据你的前端模块名进行相应的修改

mvn package 一下,后端模块的打包 jar 里面就会有相应的资源文件啦

F. 前后端联调
步骤
在前端项目 package.json 中指明接口的代理
“proxy”:”http://localhost:8080/”
如果 servletPath 不为 /,则需要在后面补上相应的 servletPath
当你的后端项目有设置 servletPath 的时候,需要相应配置前端的打包的 servletPath,否则默认为 / 的 servletpath

方法 1: package.json 增加
“homepage”: “.”

方法 2: config.paths.js 文件下修改配置
function getServedPath(appPackageJson) {
const publicUrl = getPublicUrl(appPackageJson);
// 将 / 修改为./
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : ‘./’);
return ensureSlash(servedUrl, true);
}

G. 将你创建好的项目做成脚手架
提出问题

如果每个项目都要经历上面的步骤,才能完成,那前期工作量是在太繁琐又重复
借助 maven 的 archetype 来帮你自动生成一个初始项目吧

接入步骤

按照上面的流程我们已经建好了项目
在项目目录下执行 mvn archetype:create-from-project,生成的 target 就是你的脚手架项目
cd target/generated-sources/archetype 目录下,执行 mvn install 就把 archetype 放入了本地仓库了,可以进行使用了
为了 deploy 到远程仓库中,需要在 target/generated-sources/archetype 目录下的 pom.xml 中加入自己的远程仓库的地址,然后在 target/generated-sources/archetype 目录下 mvn deploy 就可以了

屏蔽掉部分不想打包进 archetype 的文件
要屏蔽部分文件夹时在 pom 中加入 plugin
<plugin>
<artifactId>maven-archetype-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<propertyFile>archetype.properties</propertyFile>
</configuration>
</plugin>
新建 archetype.properties 文件, 配置要忽略的通配符 excludePatterns=/.idea/,**.iml
怎么使用 archetype

创建项目在 idea 中,在点击 file-> new-> project 后弹出的对话框中选择 maven
在 create from archetype 打勾,点击 Add archetype 加入创建好的 archetype
填写对应的 groupId,artifaceId,version 后在列表中选择已有的 archetype
按引导进行后续步骤的创建,然后就会自动生成跟你项目一样的啦

跨 store 的访问
什么是跨 store 访问
在上面我们有这样的代码
const routerStore = new RouterStore()
rootStore[‘app’] = new AppStore()
routerStore[‘routing’] = routerStore
有时候我们往往需要在一个 store 的方法中去访问下别的 store 的内容,这个时候就是跨 store 的访问,就需要在初始化时将 rootStore 传给这个 store,通过 rootStore 去访问, 改写 index.js
rootStore[‘app’] = new AppStore(rootStore)
改写 AppStore.js,增加构造函数
constructor(rootStore) {
this.rootStore = rootStore
}
这样就可以在 AppStore.js 的函数中通过 this.rootStore 去获取所有 store 的 json,从而访问所有的 store 了

正文完
 0