前端开发

使用react-mobx-starter-master脚手架,解压更名为frontend
在src中新增component、service、css目录
链接:https://pan.baidu.com/s/17tsX5QOrFi2j3An68L93ew
提取码:6eip
注:没有特别说明,js开发都在src目录下
在这里插入图片描述
修改项目名称
在这里插入图片描述
</font color=blue size=4>webpack.config.dev.js

devServer: {
			compress: true, /* gzip */
			//host: '192.168.142.1', // IP设置
			port: 3000,
			publicPath: '/assets/', /* 设置bundled files浏览器端访问地址 */
			hot: true, /* 开启HMR热模块替换 */
			inline: true, /* 控制浏览器控制台是否显示信息 */
			 historyApiFallback: true,
			 stats: {
			 chunks: false
			 },
			 proxy: { // 代理
			 '/api': {
			 target: 'http://127.0.0.1:8000',
			 changeOrigin: true
			 }
			 }
			 }

安装依赖

$ 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 = () => (
  <div>
    <h2>Home</h2>
  </div>
);
const About = () => (
  <div>
    <h2>About</h2>
  </div>
);
const App = () => (
  <Router>
    <div>
      <Route exact path="/" component={Home} />
      <Route exact path="/about" component={About} />
    </div>
  </Router>
);
ReactDom.render(<App />, document.getElementById('root'));

);
ReactDom.render(<App />, document.getElementById('root'));

在地址栏里面输入 http://127.0.0.1:3000/http://127.0.0.1:3000/about 试试看,能够看到页面的变化App中,使用了Router路由组件,Router是根,且它只能有一个元素,所以加了div。
Route指令

  • 它负责静态路由,只能和Route指令的path匹配,组件就可以显示。URL变化,将重新匹配路径
  • component属性设置目录组件
  • path是匹配路径,如果匹配则显示组件
    • exact:布尔值
    • strict:布尔值
  • 没有path属性,组件将总是显示,例如
  • paht属性还支持路径数组,意思是多个路径都可以匹配
import React from 'react';
import ReactDom from 'react-dom';
import {Route, Link, BrowserRouter as Router, Switch} from 'react-router-dom';
const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);
const About = () => (
  <div>
    <h2>About</h2>
  </div>
);
const Always = () => (
  <div>
    <h2>Always</h2>
  </div>
);
const App = () => (
  <Router>
    <div>
      <Route exact path={["/",'/index']} component={Home} />
      <Route exact path="/about" component={About} />
      <Route  component={Always} />
    </div>
  </Router>
);
ReactDom.render(<App />, document.getElementById('root'));

路径匹配
在这里插入图片描述
exact只能匹配本路径,不包含子路径
strict路径尾部有/,则必须匹配这个/,也可以匹配子路径
exact strict一起用,表示严格的等于当前指定路径

Switch指令
也可以将Route组织到一个Switch中,一旦匹配Swich中的一个Route,就不再匹配其它,但是Route时匹配所有,如果匹配就会显示组件,无path的Route始终匹配。

<Switch>
        <Route exact path={["/",'/index']} component={Home} />
        <Route exact path="/about/" component={About} />
        <Route  component={Always} />
      </Switch>

注意这时候Always组件,其实是404组件了,因为只有Switch中其上的Route没有匹配,才轮到它

登录组件

在component目录下构建react组件
登录页模板
https://codepen.io/colorlib/pen/rxddKy?q=login&limit=all&type=type-pens

import React from 'react'
import {Link} from 'react-router-dom'
import '../css/login.css'

export default class Login extends React.Component{
  render(){
    return (<div className="login-page">
    <div className="form">
      <form className="login-form">
        <input type="text" placeholder="邮箱"/>
        <input type="password" placeholder="密码"/>
        <button>登录</button>
        <p className="message">没有账户? <a href="#">注册账户</a></p>
      </form>
    </div>
  </div>)
  }
}

使用这个HTML模板来构建组件
特别注意

  • 搬到React组件中的时候,要将class属性改为className
  • 所有标签,需要闭合

login.js
在component目录下建立login.js的登录组件
使用上面的模板的HTML中的登录部分,挪到render函数中

  • 修改class为className
  • 将<a>标签替换成<Link to="?">组件
  • 注意标签闭合
import React from 'react'
import {Link} from 'react-router-dom'


export default class Login extends React.Component{
  render(){
    return (<div className="login-page">
    <div className="form">
      <form className="login-form">
        <input type="text" placeholder="邮箱"/>
        <input type="password" placeholder="密码"/>
        <button>登录</button>
        <p className="message">没有账户? <Link to ="/reg">注册账户</Link></p>
      </form>
    </div>
  </div>)
  }
}

index.js
在路由中增加登录组件

import React from 'react'
import {Link} from 'react-router-dom'


export default class Reg extends React.Component{
  render(){
    return (<div className="reg-page">
    <div className="form">
      <form className="register-form">
        <input type="text" placeholder="姓名"/>
        <input type="text" placeholder="邮箱"/>
        <input type="password" placeholder="密码"/>
        <input type="password" placeholder="确认密码"/>
        <button>注册账户</button>
        <p className="message">已经有账户? <Link to="/login">请登录</Link></p>
      </form>
    </div>
  </div>)
  }
}

访问 http://127.0.0.1:3000/login 就可以看到登录界面了。但是没有样式。
样式表
在src/css中,创建login.css,放入一个内容,然后在login.js中导入样式

import React from 'react'
import {Link} from 'react-router-dom'
import '../css/login.css'

如有需要,重启dev server,可以看到页面,如下(http://localhost:3000/login)

在这里插入图片描述
注册组件
与登录组件编写方式差不多,创建component/reg.js,使用login.css

import React from 'react'
import {Link} from 'react-router-dom'
import '../css/login.css'

export default class Login extends React.Component{
  render(){
    return (<div className="login-page">
    <div className="form">
      <form className="login-form">
        <input type="text" placeholder="邮箱"/>
        <input type="password" placeholder="密码"/>
        <button>登录</button>
        <p className="message">没有账户? <Link to ="/reg">请注册</Link></p>
      </form>
    </div>
  </div>)
  }
}

在index.js中增加一条静态路由

const App = () => (
  <Router>
    <div>
        <Route exact path={["/",'/index']} component={Home} />
        <Route exact path="/about" component={About} />
        <Route exact path="/login" component={Login} />
        <Route exact path="/reg" component={Reg} />
    </div>
  </Router>
);

在这里插入图片描述导航栏链接
在Index.js中增加导航链接栏,方便页面切换

 <Router>
    <div>
      <div>
        <ul>
          <li><Link to ="/">主页</Link></li>
          <li><Link to ="/login">登录</Link></li>
          <li><Link to ="/reg">注册</Link></li>
          <li><Link to ="/about">关于</Link></li>
        </ul>
      </div>
        <Route exact path={["/",'/index']} component={Home} />
        <Route exact path="/about" component={About} />
        <Route exact path="/login" component={Login} />
        <Route exact path="/reg" component={Reg} />
    </div>
  </Router>

分层

层次作用路径
视图层负责数据呈现,负责用户交互界面src/component/xxx.js
服务层负责业务逻辑处理src/service/xxx/js
Model层数据持久化

登录功能实现
view层,登录组件和用户交互,当button点击触发onClick,调用事件响应函数handleClick,handleClick中调用服务service层的login函数。
service层,负责业务逻辑处理,调用Model层数据操作函数
问题:

  • 页面提交
    按钮点击会提交,导致页面刷新。
    要阻止页面刷新,其实就是阻止提交。使用event.preventDefault()
  • 如何拿到邮箱和密码
    event.target.form返回按钮所在表单,可以看做一个数组。fm[0].value和fm[1].value就是文本框的值
  • 如何在Login组件中使用UserService实例?
    使用全局变量,虽然可以,但是不推荐
    可以在Login的构造器中通过属性注入。
    也可以在外部使用proprs注入。
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{
  handleClick(event){
    console.log(event.target)
    let fm=event.target.form
    this.props.service.login(
      fm[0].value,fm[1].value
    )
  }
  render(){
    return (<div className="login-page">
    <div className="form">
      <form className="login-form">
        <input type="text" placeholder="邮箱"/>
        <input type="password" placeholder="密码"/>
        <button onClick={this.handleClick.bind(this)}>登录</button>
        <p className="message">没有账户? <Link to ="/reg">请注册</Link></p>
      </form>
    </div>
  </div>)
  }
}
UserService的login方法实现

代理配置
修改webpack.config.dev.js文件中proxy部分,要保证proxy的target是后台服务的地址和端口,且要开启后台服务。
注意,修改这个配置,需要重启devserver

devServer: {
        compress: true,
        port: 3000,
        publicPath: '/assets/',
        hot: true,
        inline: true,
        historyApiFallback: true,
        stats: {
            chunks: false
        },
        proxy: {
            '/api': {
                target: 'http://127.0.0.1:8000',
                changeOrigin: true
            }

axios异步库
axios是一个基于Promise的HTTP异步库,可以用在浏览器或nodejs中
使用axios发起异步调用,完成POST、GET方法的数据提交。参照官网的例子
中文说明 https://www.kancloud.cn/yunye/axios/234845
安装npm

npm install axios或yarn add axios

注意,如果使用yarn安装,就不要再使用npm安装包了,以免出现问题
导入

import axios from '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/user/login,也就是多了/api
    1、修改blog server的代码的路由规则
    不建议这么做,影响有点大
    2、rewrite
    类似httpd、nginx等的rewrite功能。本次测试使用的是dev server,去官方查看
    https://webpack.js.org/configuration/dev-server/#devserver-proxy
    可以查到pathRewrite可以完成路由重写
devServer: {
		 compress: true,
		 port: 3000,
		 publicPath: '/assets/',
		 hot: true,
		 inline: true,
		 historyApiFallback: true,
		 stats: {
		 		chunks: false
		 },
		 proxy: {
		 		'/api': {
				 target: 'http://127.0.0.1:8000',
				 pathRewrite: {"^/api" : ""},
				 changeOrigin: true
			 }
		 }
	 }

重启dev server
使用正确的邮箱、密码,返回json数据,在response.data中可以看到token、user
token持久化–LocalStorage
使用LocalStorage来存储token
LocalStage是HTML5标准增加的技术,是浏览器持久化方案之一。
LocalStorage是为了存储浏览器得到的数据,例如json
数据存储是键值对
数据会存储在不同的域名下面
不同浏览器对单个域名存储数据的长度支持不同,有的最多支持2MB
在这里插入图片描述
SessionStorage和LocalStorage功能差不多,只不过SessionStorage是会话级的,浏览器关闭,会话结束,数据清除,而LocalStorage可以持久保存

IndexedDB

  • 一个域一个datatable
  • key-value检索方式
  • 建立在关系型的数据模型之上,具有索引表、游标、事务等概念

store.js
store.js是一个兼容所有浏览器的LocalStorage包装器,不需要借助Cookie或者Flash
store.js会根据浏览器自动选择使用localStorage,globalStorage或者userData来实现本地存储功能

安装

npm i store 或 yarn add store

测试代码
编写一个test.js,使用node exec插件按F8

let store=require('store')

store.set('user','xiaobai')
console.log(store.get('user'))

store.remove('user')
console.log(store.get('user'))
console.log(store.get('user','a'))

store.set('user',{name:'string'})

store.each(function(value,key){
    //注意这里key、value是反的
    console.log(key,'--->',value)
})

store.clearAll()
console.log(store.get('user'))

在这里插入图片描述
安装store的同时,也安装了expire过期插件,可以在把kv对存储到LS中的时候顺便加入过期时长

const store=require('store')
//一定要加载插件,否则key不会过期
store.addPlugin(require('store/plugins/expire'))

let d=new Date()
store.set('user','xiaobai',(new Date()).getTime()+(10*1000))
//注意时间单位
setInterval(()=>console.log(store.get('user','abc')),1000)

下面是准备写在service中的代码

import store from 'store'
import expire from 'store/plugins/expire' //过期插件

store.addPlugin(expire)
//存储token
store.set('token',res.data.token,(new Data()).getTime()+(8*3600*1000))

Mobx状态管理
Redux和Mobx
社区提供的状态管理库,有Redux和Mobx。
Redux代码优秀,使用严格的函数式编程思想,学习曲线陡峭,小项目的优势不明显
Mobx,非常优秀稳定的库,简单方便,适合中小项目使用。使用面对对象的方式,容易学习和接受。现在在中小型项目中使用也非常广泛。
Mobx官网 https://mobx.js.org/
中文网 http://cn.mobx.js.org/
Mobx是由Mendix、Coinbase、Facebook开源,实现了观察者模式
观察者模式
观察者模式,也称为发布订阅模式,观察者观察某个目标,目标对象(Obserable)状态发生了变化,就会通知自己内部注册了的观察者Observer
状态管理
需求
一个组件的onClick触发事件响应函数,此函数会调用后台服务。但是后台服务比较耗时,等处理完,需要引起组件的渲染操作
要组件渲染,就需要改变组件的props或state
1、同步说明
同步调用中,实际上就是等着耗时的函数返回

import React from 'react';
import ReactDom from 'react-dom';
class Service {
 handle(n) {
 // 同步
 console.log('pending~~~~~~~~')
 // 同步死循环
 for (let s=new Date(); new Date()-s < n*1000;);
 console.log('done');
 return Math.random();
 }
}
class Root extends React.Component {
 state = {ret:null}
 handleClick(event){
 // 同步返回值
 let ret = this.props.service.handle(4);
 this.setState({ret:ret});
 }
 render() {
 console.log('***********************');
 return (
 <div>
 <button onClick={this.handleClick.bind(this)}>触发handleClick函数</button><br />
 <span style={{color:'red'}}>{new Date().getTime()} Service的handle函数返回值是: 
{this.state.ret}</span>
 </div>);
 }
}
ReactDom.render(<Root service={new Service()} />, document.getElementById('root'));

这里使用一个死循环来模拟同步调用,来模拟耗时的等待返回的过程。
在调用过程中,整个页面鼠标不能点击了。
2、异步调用
思路一、使用setTimeout
使用setTimeout,有2个问题。
1、无法向内部的待执行函数传入参数,比如传入Root实例。
2、延时执行的函数的返回值无法取到,所以无法通知Root
思路二、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 (
 <div>
 <button onClick={this.handleClick.bind(this)}>触发handleClick函数</button><br />
 <span style={{color:'red'}}>{new Date().getTime()} Service中修改state的值是: 
{this.state.ret}</span>
 </div>);
 }
}
ReactDom.render(<Root service={new Service()} />, document.getElementById('root'));

不管render中是否显示state的值,只要state改变,都会触发render执行
3、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 (
 <div>
 <button onClick={this.handleClick.bind(this)}>触发handleClick函数</button><br />
 <span style={{color:'red'}}>{new Date().getTime()} Service中修改state的值是: 
{this.props.service.ret}</span>
 </div>);
 }
}
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 <Redirect to='/' />; //to表示跳转到哪里
login登陆功能代码实现
// service/user.js
import axios from 'axios';
import store from 'store'
import expire from 'store/plugins/expire'
import {observable} from 'mobx'

//过期插件
store.addPlugin(expire)
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.data);
        console.log(response.status);
        // 存储token,注意需要重开一次调试窗口才能看到
        store.set(
            'token',response.data.token,
            (new Date()).getTime()+(8*3600*1000))
            this.loggedin=true //修改被观察者
    }
    ).catch(
        function (error) {
        console.log(error);
    }
    )
    }
    }
//component/login.js
import React from 'react'
import {Link} from 'react-router-dom'
import '../css/login.css'
import UserService from '../service/user'
import {observer} from 'mobx-react'

const service=new UserService()

export default class Login extends React.Component{
  render(){
    return <_Login service={service}/>
  }
}
@observer
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(){
    if (this.props.service.loggedin){
      return <Redirect to='/' /> //跳转
    }
    return (<div className="login-page">
    <div className="form">
      <form className="login-form">
        <input type="text" placeholder="邮箱"/>
        <input type="password" placeholder="密码"/>
        <button onClick={this.handleClick.bind(this)}>登录</button>
        <p className="message">没有账户? <Link to ="/reg">请注册</Link></p>
      </form>
    </div>
  </div>)
  }
}

注意,测试时,开启Django编写的后台服务程序。
测试成功,成功登录,写入Localstorage,也实现了跳转

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值