React Project: Step11 Auth

本文详细介绍了如何在React项目中实现Firebase的认证功能,包括创建Auth界面、设置表单提交、使用Firebase API进行登录注册、错误处理、登录状态管理、注销逻辑以及权限控制。同时,文章还涵盖了在登录前后页面重定向的实现细节。
摘要由CSDN通过智能技术生成

新建Auth界面

类似checkourder的表单;
对input的value和state进行双向绑定;
且加入有效检查‘

import React,{Component} from 'react';
import Input from '../../components/UI/Input/Input';
import Button from '../../components/UI/Button/Button';
import classes from './Auth.css';
class Auth extends Component{
    state={
        controls:{
            email: {
                elementType:'input',
                elementConfig:{
                    type:'email',
                    placeholder:'Mail Address'
                },
                value:'',
                validation:{
                    required:true,
                    isEmail:true
                },
                valid:false,
                touched:false
            },
            password: {
                elementType:'input',
                elementConfig:{
                    type:'password',
                    placeholder:'Password'
                },
                value:'',
                validation:{
                    required:true,
                    minLength:6
                },
                valid:false,
                touched:false
            }
        }
    }

    checkValidity(value, rules){
        let isValid= true;
        if(rules.required&&isValid){
            isValid= value.trim()!=='';   //.trim()可以可以防止空格
        }

        if(rules.minLength&&isValid){
            isValid= value.length>=rules.minLength;
        }

        if(rules.maxLength&&isValid){
            isValid= value.length<=rules.maxLength;
        };

        return isValid;
    };


    inputChangedHandler = (event, controlName)=>{
        const updatedControls = {
            ...this.state.controls,
            [controlName]:{
                ...this.state.controls[controlName],
                value:event.target.value,
                //使用检查函数
                valid:this.checkValidity(event.target.value, this.state.controls[controlName].validation),
                touched:true   //标记为输入过
            }
        };
        this.setState({controls:updatedControls});
    }


    render(){
        const formElementArray = [];
        for(let key in this.state.controls){     //遍历每个name
            formElementArray.push({
                id:key,
                config:this.state.controls[key]
            });
        };

        const form = formElementArray.map(formElement=>(
            <Input 
                key={formElement.id}
                elementType={formElement.config.elementType} 
                elementConfig={formElement.config.elementConfig} 
                value={formElement.config.value}
                changed={(event)=>this.inputChangedHandler(event,formElement.id)}
                invalid={!formElement.config.valid}
                shouldValidate={formElement.config.validation&& formElement.config.touched}
            />
        ));

        return(
            <div className={classes.Auth}>
                <form>
                    {form}
                    <Button btnType="Success">SUBMIT</Button>
                </form>
            </div>
        );
    };
};

export default Auth;

在这里插入图片描述
在navigtaion中加入此component——link;

import React from 'react';

import classes from './NavigationItems.css';
import NavigationItem from './NavigationItem/NavigationItem';

const navigationItems = () => (
    <ul className={classes.NavigationItems}>
        <NavigationItem link="/" exact>Burger Builder</NavigationItem>
        <NavigationItem link="/orders">Orders</NavigationItem>
        <NavigationItem link="/auth">Authenticate</NavigationItem>
    </ul>
);

export default navigationItems;

在app.js中加入此route;

import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import Layout from './hoc/Layout/Layout';
import BurgerBuilder from './containers/BurgerBuilder/BurgerBuilder';
import Checkout from './containers/Checkout/Checkout';
import Orders from './containers/Orders/Orders';

import Auth from './containers/Auth/Auth';

class App extends Component {
  render () {
    return (
      <div>
        <Layout>
          <Switch>
            <Route path="/checkout" component={Checkout}/>
            <Route path="/orders" component={Orders}/>
            <Route path="/auth" component={Auth}/>
            <Route path="/" exact component={BurgerBuilder}/>
          </Switch>
        </Layout>
      </div>
    );
  }
}

export default App;

设置form的onSubmit参数

Auth/js
引入action和connect;

import * as actions from '../../store/actions/index';
import {connect} from 'react-redux';

form加入onsubmit——调用函数;

    submitHandler = (event)=>{
        event.preventDefault();
        this.props.onAuth(this.state.controls.email.value,this.state.controls.password.value);
        
    }
return(
            <div className={classes.Auth}>
                <form onSubmit={this.submitHandler}>
                    {form}
                    <Button btnType="Success">SUBMIT</Button>
                </form>
            </div>
        );

将redux中的action转为props传入此js;

const mapDispatchToPoprs =dispatch=>{
    return {
        onAuth:(email,password)=>dispatch(actions.auth(email,password))
    };
};
export default connect(null,mapDispatchToPoprs)(Auth);

使用firebase 的 api,进行signin和signup

https://firebase.google.com/docs/reference/rest/auth#section-create-email-password
在这里插入图片描述在auth中执行异步调用

//用于auth的异步函数
export const auth = (email, password) =>{
    return dispatch=>{
        dispatch(authStart());
        const authData= {
            email: email,
            password:password,
            returnSecureToken: true
        }
        axios.post('https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=',authData)
                .then(response=>{
                    console.log(response);
                    dispatch(authSuccess(response.data));
                })
                .catch(err=>{
                    console.log(err);
                    dispatch(authFail(err));
                })
    };
};

成功完成post操作;

控制sign in

在这里插入图片描述
设定新状态——判断当前是signup还是signin

    switchAuthModeHandler = ()=>{
        this.setState(prevState=>{
            return {isSignup: !prevState.isSignup}
        })
    }
<Button 
        clicked={this.switchAuthModeHandler}
        btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>

根据state中的signin标记,进行不同的url调用

//用于auth的异步函数
export const auth = (email, password, isSignup) =>{   //传入需要signup还是signin
    return dispatch=>{
        dispatch(authStart());
        const authData= {
            email: email,
            password:password,
            returnSecureToken: true
        };
        let url='https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=';
        if(!isSignup){
            url='https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=';
        }
        axios.post(url,authData)
                .then(response=>{
                    console.log(response);
                    dispatch(authSuccess(response.data));
                })
                .catch(err=>{
                    console.log(err);
                    dispatch(authFail(err));
                })
    };
};

将signin后从db中得到的用于token和id保存在auth的state中

调用authsuccess时传入id和token;
return时保存在redux的state中;

import * as actionTypes from './actionsTypes';
import axios from 'axios';

export const authStart = ()=>{      //用于loading  spinner
    return {
        type:actionTypes.AUTH_STRAT
    };
};

export const authSuccess = (token,userId)=>{
    return{
        type:actionTypes.AUTH_SUCCESS,
        idToken:token,
        userId:userId
    };
};

export const authFail = (error)=>{
    return{
        type: actionTypes.AUTH_FAIL,
        error:error
    };
};

//用于auth的异步函数
export const auth = (email, password, isSignup) =>{   //传入需要signup还是signin
    return dispatch=>{
        dispatch(authStart());
        const authData= {
            email: email,
            password:password,
            returnSecureToken: true
        };
        let url='https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=';
        if(!isSignup){
            url='https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=';
        }
        axios.post(url,authData)
                .then(response=>{
                    console.log(response);
                    dispatch(authSuccess(response.data.idToken,response.data.localId));
                })
                .catch(err=>{
                    console.log(err);
                    dispatch(authFail(err));
                })
    };
};

在signin&up等待异步调用的时候加入spinner

在Auth中加入reducer和store

import * as actions from '../../store/actions/index';
import {connect} from 'react-redux';
import Spinner from '../../components/UI/Spinner/Spinner';

判断loading是否为true;

        if(this.props.loading){
            form=<Spinner/>;
        }

将spinner加入return中;

        return(
            <div className={classes.Auth}>
                {errorMessage}
                <form onSubmit={this.submitHandler}>
                    {form}
                    <Button btnType="Success">SUBMIT</Button>
                </form>
                <Button 
                    clicked={this.switchAuthModeHandler}
                    btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
            </div>
        );

需要传入的state和action;

//需要得到reducer中的state
//进行signup & in时都会改变loading,调用loading即可
const mapStateToProps = state=>{
    return {
        loading:state.auth.loading,
        error:state.auth.error
    };
};

const mapDispatchToPoprs =dispatch=>{
    return {
        onAuth:(email,password,isSignup)=>dispatch(actions.auth(email,password,isSignup))
    };
};

export default connect(mapStateToProps,mapDispatchToPoprs)(Auth);

错误处理

firebase提供的错误信息;
在这里插入图片描述
将props中保存的error在auth页面中提出;

        let errorMessage = null;
        if(this.props.error){
            errorMessage = (
                <p>{this.props.error.message}</p>   //从firebase获取的error
            )
        };
return(
            <div className={classes.Auth}>
                {errorMessage}
                <form onSubmit={this.submitHandler}>
                    {form}
                    <Button btnType="Success">SUBMIT</Button>
                </form>
                <Button 
                    clicked={this.switchAuthModeHandler}
                    btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
            </div>
        );

保存error——action
axios中得到err.response
得到firebase提供的error.data.error,里面的message即为错误信息;

//用于auth的异步函数
export const auth = (email, password, isSignup) =>{   //传入需要signup还是signin
    return dispatch=>{
        dispatch(authStart());
        const authData= {
            email: email,
            password:password,
            returnSecureToken: true
        };
        let url='https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=';
        if(!isSignup){
            url='https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=';
        }
        axios.post(url,authData)
                .then(response=>{
                    console.log(response);
                    dispatch(authSuccess(response.data.idToken,response.data.localId));
                })
                .catch(err=>{
                    dispatch(authFail(err.response.data.error));   //axios提供的err调用方法
                });
    };
};

设定Logout——定时器

actiontype

//logout
export const AUTH_LOGOUT = 'AUTH_LOGOUT';

action

export const logout=()=>{
    return {
        type: actionTypes.AUTH_LOGOUT
    };
};

export const checkAuthTimeout=(expirationTime)=>{
    return dispatch=>{
        setTimeout(() => {
            dispatch(logout());
        }, expirationTime * 1000); //ms 转为 s
    };
};

异步访问——成功时设置定时器;

        axios.post(url,authData)
                .then(response=>{
                    console.log(response);
                    dispatch(authSuccess(response.data.idToken,response.data.localId));
                    //同时设置登录计时
                    dispatch(checkAuthTimeout(response.data.expiresIn));
                })
                .catch(err=>{
                    dispatch(authFail(err.response.data.error));   //axios提供的err调用方法
                });

设置firebase中的访问条件

可以访问ingredients;
需要auth才能访问orders;

{
  "rules": {
    "ingredients":{
      ".read": true,
      ".write": true
    },
    "orders":{
      ".read":"auth != null",
      ".write":"auth != null"
    }
  }
}

在获取token后的post和get order操作中,传入auth=token

order.js
token参数都为上一层调用connect后从reducer的state中传入;
或者也可在dispatch旁加入getState参数,得到当前的state,调用state.token即可;

export const fetchOrders= (token)=>(dispatch)=>{       //actioncreator函数——返回一个action
        dispatch(fetchOrdersStart());
        //加入auth操作
        axios.get('/orders.json?auth='+token)
            .then(res=>{
                const fetchOrders=[];
                for(let key in res.data){
                    fetchOrders.push({
                        ...res.data[key],
                        id:key
                    });
                }
                dispatch(fetchOrdersSuccess(fetchOrders));
            })
            .catch(error=>{
                dispatch(fetchOrdersFailed(error));
            });
};
//async        不返回一个action,返回一个调用action的异步函数
export const purchaseBurger = (orderData, token) => {        
    return dispatch=>{
        dispatch(purchaseBurgerStart());//在异步调用前改变loading——要用dispatch包裹
        axios.post( '/orders.json?auth='+token, orderData )
        .then( response => {
            console.log(response.data);  //name才是真正的id
            dispatch(purchaseBurgerSuccess(response.data.name,orderData));
        } )
        .catch( error => {
            dispatch(purchaseBurgerFail(error));
        } );
    };
};

设置NAV-authenticate在获取token后变为NAV-Logout

import React from 'react';
import classes from './NavigationItems.css';
import NavigationItem from './NavigationItem/NavigationItem';
//只有class extends component 才能用connect
const navigationItems = (props) => (
    <ul className={classes.NavigationItems}>
        <NavigationItem link="/" exact>Burger Builder</NavigationItem>
        <NavigationItem link="/orders">Orders</NavigationItem>
        {props.isAuthenticated
            ?<NavigationItem link="/logout">Logout</NavigationItem>
            :<NavigationItem link="/auth">Authenticate</NavigationItem>
        }
    </ul>
);

export default navigationItems;

选取调用该navigationitems的container,connect该container,并向下传入auth状态,根据状态显示logout或authentic;
layout——》toolbar——》navigationitems

Logout 后redirect回主界面+清空token

Logout.js

import React,{Component} from 'react';
import * as actions from '../../../store/actions/index';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';

class Logout extends Component {
    componentDidMount(){
        this.props.onLogout();
    };

    render(){
        return(
            <Redirect path="/"/>
        );
    };
};

const mapDispatchToProps = dispatch=>{
    return{
        onLogout: ()=>dispatch(actions.logout())
    };
}

export default connect(null,mapDispatchToProps)(Logout);

App.js中加入该route

<Route path="/logout" component={Logout}/>

问题:并没有清除state中保存的order内容

在signin前隐藏orders

Navigtaionitems.js

        <NavigationItem link="/" exact>Burger Builder</NavigationItem>
        {
            props.isAuthenticated
            ?<NavigationItem link="/orders">Orders</NavigationItem>
            :null
        }

在signin成功后redirect到ingredients

Auth .js

        let authRedirect = null;
        if(this.props.isAuthenticated){
            authRedirect=<Redirect to='/'/>
        };

        return(
            <div className={classes.Auth}>
                {authRedirect}
                {errorMessage}
                <form onSubmit={this.submitHandler}>
                    {form}
                    <Button btnType="Success">SUBMIT</Button>
                </form>
                <Button 
                    clicked={this.switchAuthModeHandler}
                    btnType="Danger">SWITCH TO {this.state.isSignup? 'SIGNIN':"SIGNUP"}</Button>
            </div>
        );

在Sign In 之前,ingredients页面显示signup ,并重定向到注册页面

BurgerBuilder.js
根据auth来定义该按钮的操作——进入purchasing 或者 重定向到注册/登录页面;

    purchaseHandler = () => {
        if(this.props.isAuthenticated){
            this.setState( { purchasing: true } );
        }else{
            this.props.history.push('/auth');
        }
    }

BuildControls.js
根据传入的autrh显示按钮的输出内容;

<button 
            className={classes.OrderButton}
            disabled={!props.purchasable}
            onClick={props.ordered}>{props.isAuth?'ORDER NOW':'SIGN UP TO ORDER'}</button>

signin&up界面重定向的逻辑:

  1. 若从建立汉堡的signup to cotinue界面进入,则sign in 后直接定向到checkout界面;
  2. 若从auth进入,sign in 后直接定向到建立汉堡界面;

状态1:auth.js
state保存当前需要重定向的地址;

    authRedirectPath: '/'

函数

const setAuthRedirectPath = (state,action)=>{
    return updateObejct(state,{authRedirectPath:action.path})
};

reducer:

case actionTypes.SET_AUTH_REDIRECT_PATH: return setAuthRedirectPath(state,action);

action(用于在container中被调用):

//将path传入reducer,作为auth的state
export const setAuthRedirectPath = (path)=>{
    return {
        type:actionTypes.SET_AUTH_REDIRECT_PATH,
        path:path
    };
};

Burgerbuilder.js中
若为点击sign up to purchase,则将authstate中国的path改为checkout;

    purchaseHandler = () => {
        if(this.props.isAuthenticated){
            this.setState( { purchasing: true } );
        }else{
            this.props.onSetAuthRedirectPath('/checkout');
            this.props.history.push('/auth');
        }
    };

状态2:buildingburger——判断是否建立汉堡
burgerbuilder——state;
在添加和删除时改为true;
在返回构建页面时,改为false;

    building:false  //添加/删除后改为true

最终的条件判断——Auth.js
当满足 (没有重新回到build页面&&从build页面的signup按钮过来)时,登录后直接定位到checkout界面;

    componentDidMount(){
        //没有buildburger并且访问了该页面并且初始path不为‘/’
        if(!this.props.buildingBurger && this.props.authRedirectPath!=='/'){
            this.props.onSetAuthRedirectPath();  //将默认path改为'.'
        }
    };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值