前端开发及登录功能实现(项目源码:https://github.com/sqsltr520/python)

1. 前端开发环境设置

使用react-mobx-starter-master脚手架,解压更名为frontend。在src中新增componentservicecss目录。

修改项目信息:

{
"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

1.1 前端路由

前端路由使用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'));

Route指令:

  • 它负责静态路由,只能和Route指令的path匹配,组件就可以显示。URL变化,将重新匹配路径
  • component属性设置目标组件
  • path是匹配路径,如果匹配则显示组件:exact:布尔值 ;strict:布尔值
  • 没有path属性,组件将总是显示,例如 <Route component={Always} />
  • path属性还支持路径数组,意思是多个路径都可以匹配

路径匹配:

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

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

 

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

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

特别注意 :

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

login.js

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

  • <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 Login from './component/login';


const App = () => (
<Router>
    <div>
        <Route path="/about" component={About} />
        <Route path="/login" component={Login} />
        <Route exact path="/" component={Home} />
    </div>
</Router>
);

注册组件:与登录组件编写方式差不多,创建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 (
            <div className="login-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>
        );
    }
}

分层:

1.2 登录功能的实现

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

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 (<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>);
    }
}

问题:

  • 页面提交 ,这一次发现有一些问题,按钮点击会提交,导致页面刷新了。 要阻止页面刷新,其实就是阻止提交。使用event.preventDefault()
  • 如何拿到邮箱和密码? event.target.form返回按钮所在表单,可以看做一个数组。 fm[0].valuefm[1].value就是文本框的值。
  • 如何在Login组件中使用UserService实例呢? 使用全局变量,虽然可以,但是不好。 可以在Login的构造器中通过属性注入。 也可以在外部使用props注入。使用这种方式。
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 (<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>);
    }
}

1.3 UserServicelogin方法实现

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

axios异步库:axios是一个基于PromiseHTTP异步库,可以用在浏览器或nodejs中。 使用axios发起异步调用,完成POSTGET方法的数据提交。可参照官网的例子。中文说明 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的代码的路由匹配规则,不建议这么做,影响有点大

2rewrite,类似httpdnginx等的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中可以看到tokenuser

store.js:store.js 是一个兼容所有浏览器的 LocalStorage 包装器,不需要借助 Cookie 或者 Flashstore.js 会根据浏览器自动选择使用 localStorageglobalStorage 或者 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)

 1.4 Mobx状态管理

社区提供的状态管理库,有ReduxMobx。Redux代码优秀,使用严格的函数式编程思想,学习曲线陡峭,小项目使用的优势不明显。 Mobx,非常优秀稳定的库,简单方便,适合中小项目使用。使用面向对象的方式,容易学习和接受。现在在中小 项目中使用也非常广泛。MobxReact也是一对强力组合。

Mobx官网 https://mobx.js.org/

中文网 http://cn.mobx.js.org/

MobX 是由 MendixCoinbaseFacebook 开源,它实现了观察者模式。

观察者模式,也称为发布订阅模式,观察者观察某个目标,目标对象(Obserable)状态发生了变化,就会通知自己内部注册了的观察者Observer

状态管理 :

需求 :一个组件的onClick触发事件响应函数,此函数会调用后台服务。但是后台服务比较耗时,等处理完,需要引起组

件的渲染操作。要组件渲染,就需要改变组件的propsstate

异步调用:思路一、使用setTimeout ,使用setTimeout,有2个问题。

  • 无法向内部的待执行函数传入参数,比如传入Root实例。
  • 延时执行的函数的返回值无法取到,所以无法通知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执行。

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函数中,一定要使用这个被观察对象。

跳转:如果serviceret发生了变化,观察者Login就会被通知到。一般来说,就会跳转到用户界面,需要使用Redirect

件。

// 导入Redirect
import {Link, Redirect} from 'react-router-dom';
//render函数中return
return <Redirect to='/' />; //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} 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值