使用react-mobx-starter-master脚手架,解压更名为frontend。在src中新增component、service、css目录。
修改项目信息:
{
"name": "blog",
"description": "blog project",
"author": "sun"
}
webpack.confifig.dev.js
devServer: {
compress: true,
port: 3001,
publicPath: '/assets/',
hot: true,
inline: true,
historyApiFallback: true,
stats: {
chunks: false
},
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
pathRewrite: {"^/api": ""} # 路径重写
}
}
}
安装依赖:$ npm install
npm会安装package.json中依赖的包。也可以使用新的包管理工具yarn安装模块
yarn安装
$ npm install -g yarn
或者,去 https://yarn.bootcss.com/docs/install/
相当于 npm install
$ yarn
相当于npm install react-router
$ yarn add react-router
$ yarn add react-router-dom
前端路由使用react-router组件完成
官网文档 https://reacttraining.com/react-router/web/guides/quick-start
基本例子 https://reacttraining.com/react-router/web/example/basic
使用react-router组件,更改src/index.js
import React from 'react';
import ReactDom from 'react-dom';
import {Route, Link, BrowserRouter as Router, Switch} from 'react-router-dom';
const Home = () => (
Home
);
const About = () => (
About
);
const App = () => (
);
ReactDom.render( , document.getElementById('root'));
Route指令:
路径匹配:
exact 只能匹配本路径,不包含子路径;strict 路径尾部有/,则必须匹配这个/,也可以匹配子路径 ;exact strict 一起用,表示严格的等于当前指定路径
Switch指令:也可以将Route组织到一个Switch中,一旦匹配Switch中的一个Route,就不再匹配其它。但是Route是匹配所 有,如果匹配就会显示组件,无path的Route始终匹配。
登录组件:在component目录下构建react组件。登录页模板 https://codepen.io/colorlib/pen/rxddKy?q=login&limit=all&type=type-pens;
特别注意 :
login.js
在component目录下建立login.js的登录组件。 使用上面的模板的HTML中的登录部分,挪到render函数中。 修改class为className
import React from 'react';
import {Link} from 'react-router-dom';
export default class Login extends React.Component {
render() {
return (
);
}
}
index.js:在路由中增加登录组件:
import Login from './component/login';
const App = () => (
);
注册组件:与登录组件编写方式差不多,创建component/reg.js,使用login.css
import React from 'react';
import { Link } from 'react-router-dom';
import '../css/login.css'
export default class Reg extends React.Component {
render() {
return (
);
}
}
分层:
在src/service/user.js
export default class UserService {
login (email, password) {
// TODO
}
}
src/component/login.js:
// src/component/login.js
import React from 'react';
import {Link} from 'react-router-dom';
import '../css/login.css'
export default class Login extends React.Component {
handleClick(event) {
console.log(event.target)
}
render() {
return (
);
}
}
问题:
import React from 'react';
import {Link} from 'react-router-dom';
import '../css/login.css'
import UserService from '../service/user';
const service = new UserService();
export default class Login extends React.Component {
render() {
return <_Login service={service} />;
}
}
class _Login extends React.Component {
handleClick(event) {
event.preventDefault();
let fm = event.target.form;
this.props.service.login(
fm[0].value, fm[1].value
);
}
render() {
return (
);
}
}
代理设置:修改webpack.confifig.dev.js文件中proxy部分,要保证proxy的target是后台服务的地址和端口,且要开启后台服 务。注意,修改这个配置,需要重启dev server
axios异步库:axios是一个基于Promise的HTTP异步库,可以用在浏览器或nodejs中。 使用axios发起异步调用,完成POST、GET方法的数据提交。可参照官网的例子。中文说明 https://www.kancloud.cn/yunye/axios/234845
安装:$ yarn add axios
service/user.js修改如下:
import axios from 'axios';
export default class UserService {
login (email, password) {
console.log(email, password);
axios.post('/api/users/login', {
email:email,
password:password
})/* dev server会代理 */
.then(
function (response) {
console.log(response);
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
}
).catch(
function (error) {
console.log(error);
}
)
}
}
问题:
404:填入邮箱、密码,点击登录按钮,返回404,查看Python服务端,访问地址是 /api/users/login ,也就是多
了/api。如何解决?
1、修改blog server的代码的路由匹配规则,不建议这么做,影响有点大
2、rewrite,类似httpd、nginx等的rewrite功能。本次测试使用的是dev server,去官方看看。https://webpack.js.org/confifiguration/dev-server/#devserver-proxy
可以查到pathRewrite可以完成路由重写。
devServer: {
compress: true,
port: 3001,
publicPath: '/assets/',
hot: true,
inline: true,
historyApiFallback: true,
stats: {
chunks: false
},
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
pathRewrite: {"^/api": ""}
}
}
}
重启dev server。使用正确的邮箱、密码登录,返回了json数据,在response.data中可以看到token、user。
store.js:store.js 是一个兼容所有浏览器的 LocalStorage 包装器,不需要借助 Cookie 或者 Flash。 store.js 会根据浏览器自动选择使用 localStorage、globalStorage 或者 userData 来实现本地存储功能。
安装:$ yarn add store
let store = require('store')
store.set('user', 'sun')
console.log(store.get('user'))
store.remove('user')
console.log(store.get('user'))
console.log(store.get('user', 'a'))
store.set('user', {name:'sun', age: 29})
console.log(store.get('user').name)
store.each(function(value, key) {
console.log(key, value)
})
// user { name: 'sun', age: 29 }
store.clearAll() // 清除所有
console.log(store.get('user')) // undefined
const store = require('store')
store.addPlugin(require('store/plugins/expire'))
// 一定要加载插件,否则key永远不会过期
let d = new Date()
store.set('user', 'sun', (new Date()).getTime() + (5 * 1000)) // 注意时间单位
setInterval(() => console.log(store.get('user', 'abc')), 1000)
社区提供的状态管理库,有Redux和Mobx。Redux代码优秀,使用严格的函数式编程思想,学习曲线陡峭,小项目使用的优势不明显。 Mobx,非常优秀稳定的库,简单方便,适合中小项目使用。使用面向对象的方式,容易学习和接受。现在在中小 项目中使用也非常广泛。Mobx和React也是一对强力组合。
Mobx官网 https://mobx.js.org/
中文网 http://cn.mobx.js.org/
MobX 是由 Mendix、Coinbase、Facebook 开源,它实现了观察者模式。
观察者模式,也称为发布订阅模式,观察者观察某个目标,目标对象(Obserable)状态发生了变化,就会通知自己内部注册了的观察者Observer。
状态管理 :
需求 :一个组件的onClick触发事件响应函数,此函数会调用后台服务。但是后台服务比较耗时,等处理完,需要引起组
件的渲染操作。要组件渲染,就需要改变组件的props或state。
异步调用:思路一、使用setTimeout ,使用setTimeout,有2个问题。
思路二、Promise异步执行:Promise异步执行,如果成功执行,将调用回调。
import React from 'react';
import ReactDom from 'react-dom';
class Service {
handle(obj) {
// Promise
new Promise((resolve, reject) => {
setTimeout(() => resolve('OK'), 5000);
}).then(
value => { // 使用obj
obj.setState({
ret: (Math.random() * 100)
});
}
)
}
}
class Root extends React.Component {
state = {
ret: null
}
handleClick(event) {
// 异步不能直接使用返回值
this.props.service.handle(this);
}
render() {
console.log('***********************');
return (
{
new Date().getTime()
}
Service中修改state的值是: {
this.state.ret
}
);
}
}
ReactDom.render( < Root service = {new Service()}/>, document.getElementById('root'));
不管render中是否显示state的值,只要state改变,都会触发render执行。
Mobx实现 :
observable装饰器:设置被观察者
observer装饰器:设置观察者,将React组件转换为响应式组件
import React from 'react';
import ReactDom from 'react-dom';
import {
observable
} from 'mobx';
import {
observer
} from 'mobx-react';
class Service {
@observable ret = -100;
handle() {
// Promise
new Promise((resolve, reject) => {
setTimeout(() => resolve('OK'), 2000);
}).then(
value => {
this.ret = (Math.random() * 100);
console.log(this.ret);
}
)
}
}
@observer // 将react组件转换为响应式组件
class Root extends React.Component {
//state = {ret:null} // 不使用state了
handleClick(event) {
// 异步不能直接使用返回值
this.props.service.handle(this);
}
render() {
console.log('***********************');
return (
{
new Date().getTime()
}
Service中修改state的值是: {
this.props.service.ret
}
);
}
}
ReactDom.render( < Root service = {new Service()}/>, document.getElementById('root'));
Service中被观察者ret变化,导致了观察者调用了render函数。被观察者变化不引起渲染的情况:将上例中的 {this.props.service.ret} 注释 {/*this.props.service.ret*/} 。可以看到,如果在render中不使用这个被观察者,render函数就不会调用。在观察者的render函数中,一定要使用这个被观察对象。
跳转:如果service中ret发生了变化,观察者Login就会被通知到。一般来说,就会跳转到用户界面,需要使用Redirect组
件。
// 导入Redirect
import {Link, Redirect} from 'react-router-dom';
//render函数中return
return ; //to表示跳转到哪里
login登录功能代码实现:
import axios from 'axios'
import store from 'store'
import expire from 'store/plugins/expire'
import { observable } from 'mobx';
import '../css/login.css'
// const store = require('store')
// store.addPlugin(require('store/plugins/expire')) // 这一一定要引一下,否则不会有过期时间
store.addPlugin(expire)
export default class UserService {
// @observable ret = 0 // 这个值的变化会引起login组件的render
// @observable ret = ''
@observable loggedin = false
@observable errMsg = ''
@observable reged = false;
login(email, password){
// todo
console.log(email, password)
axios.post('/api/users/login', {
email: email,
password: password
}).then(
response => { {/* 此函数与要解决this的问题,具体参考this的坑 */}
// console.log(response)
console.log(response.data)
console.log(response.status)
// console.log(response.statusText)
let token = response.data.token
store.set('token', token, (new Date()).getTime() + (8 * 3600 * 1000)) // 设置token过期时间
// let ret = Math.random() * 1000 + 1000
// console.log(ret)
// this.ret = response.data.user.name
// console.log(this.ret)
this.loggedin = true
}
).catch(
error => { // this的坑
const {error: err=''} = error.response.data // err是别名
console.log(err)
this.errMsg = err
}
)
}
reg(name, email, password) {
console.log(name, email, password)
axios.post('/api/users/', {
email: email,
password: password,
name: name
}).then(
response => { {/* 此函数与要解决this的问题,具体参考this的坑 */}
// console.log(response)
console.log(response.data)
console.log(response.status)
// console.log(response.statusText)
let token = response.data.token
store.set('token', token, (new Date()).getTime() + (8 * 3600 * 1000)) // 设置token过期时间
// let ret = Math.random() * 1000 + 1000
// console.log(ret)
// this.ret = response.data.user.name
// console.log(this.ret)
this.reged = true
}
).catch(
error => {
const {error: err=''} = error.response.data // err是别名
console.log(err)
this.errMsg = err
}
)
}
}
const userService = new UserService()
export {userService}