# React技巧

# React实现全局组件的方式

# 简单实现一个Loading组件

需求分析:

  1. Loading 不需要同页面一起调用,而是根据需要被随时调用
  2. 调用 Loading 的 show() 和 hide() 全局方法

# 1. 创建文件

创建Loading文件夹,然后创建Loading.jsindex.jsLoading.css

# 2. 编写 index.js文件

import React from 'react'
import ReactDOM from 'react-dom'
import Loading from './loading' // 引入Loading组件
import './loading.css' // 引入Loading组件的css样式

// 1. 创建挂载组件方法
function createNotification() {
    const div = document.createElement("div");
    document.body.appendChild(div);
    const notification = ReactDOM.render(<Loading />,div);
    // notification = Loading组件
    return {
        addTitle(title) {
            // 调用 Loading组件中的 getTitle
            return notification.getTitle(title);
        },
        destroy() { // 销毁组件
            // 方式一:ReactDOM.unmountComponentAtNode(div)
            // 方式二:
            document.body.removeChild(div)
        }
    }
}

// 2. 调用组件渲染显示方法
var notification = false;
const LoadingShow = (title) => {
	// 如果  notification 不存在,将 createNotification 赋给 notification
    // 此时的 notification = createNotification()方法
    if(!notification) notification = createNotification()
    // 调用 createNotification()方法中的addTitle()方法,
    return notification.addTitle(title)
}

// 3. 调用组件销毁隐藏方法
const LoadingHide = () => {
    notification.destroy()
    notification = false
    return;
}

// 4. 将方法模块暴露
export default {
    Show(title) {
        return LoadingShow(title)
    },
    Hide() {
        return LoadingHide()
    }
}

# 3. 编写loading.js组件 和 样式

.loading {
  margin: 50px auto;
  width: 90%;
  height: 50px;
  background-color: pink;
  color: #fff;
  text-align: center;
  line-height: 50px;
}
// loading组件就很简单了

import React from 'react'

class Loading extends React.Component {
    constructor() {
        super()
        this.state = {
            title: ''
        }
    }
    getTitle(title) {
        this.setState({
            title
        })
    }
    render() {
        return (
        	<div className="loading" >
            	{this.state.title}
            </div>
        )
    }
}

# 4. 使用

// App.js
import React from 'react';
import Loading from './components/loading'
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <button onClick={() => { Loading.Show("加载页面") }}>Loading</button>
        <button onClick={() => { Loading.Hide() }}>关闭Loading</button>
      </header>
    </div>
  );
}
export default App;

# 实现一个功能较全的Toast全局组件

# 1、创建Toast文件夹,分别创建 index.js,toast.js,toast.css

# 2、编写index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Toast from './toast';
import './toast.scss';
const div = document.createElement("div");
const component = ReactDOM.render(<Toast />, div);
document.body.append(div);
function createToast() {
    return {
        addComponent(toast) {
            if (toast.duration > 0) {
                setTimeout(() => {
                    notification = false;
                    document.body.removeChild(div);
                }, toast.duration);
            }
            return component.addComponent(toast);
        },
        destory() {
            document.body.removeChild(div);
        }
    }


}
createToast()
let notification = false;
/**
  type: Toast类型
  content: 文字
  duration:显示时间
  onClose: 回调事件
*/
const toastFunc = (type, content, duration = 2000, onClose) => {
    if (!notification) notification = createToast();
    return notification.addComponent({ type, content, duration, onClose })
}

const hide = () => {
    notification.destory()
    notification = false;
    return;
}
export default {
    info(content, duration, onClose) {
        return toastFunc('info', content, duration, onClose);
    },
    success(content = "操作成功", duration, onClose) {
        return toastFunc('success', content, duration, onClose);
    },
    error(content, duration, onClose) {
        return toastFunc('error', content, duration, onClose);
    },
    loading(content = '加载中...', duration = 0, onClose) {
        return toastFunc('loading', content, duration, onClose)
    },
    hide() {
        return hide()
    }
}

# 3、编写Toast.js

import React from 'react'

class ToastBox extends React.Component {
    constructor() {
        super()
        this.transitionTime = 300
        this.state = {
            notices: {}
        }
    }
    getNoticeKey() {
        const { notices } = this.state
        return `notice-${new Date().getTime()}-${notices.length}`
    }

    addComponent(notice) {
        this.setState({ notices: notice })
    }
    render() {
        const { notices } = this.state
        const icons = {
            info: 'toast_info',
            success: 'toast_success',
            error: 'toast_error',
            loading: 'toast_loading'
        }
        if(JSON.stringify(notices) === "{}" ) {
            return null;
        } else {
            return (
                <div className="toast">
                    {
                        <div className="toast_bg">
                            <div className='toast_box'>
                                <div className={`toast_icon ${icons[notices.type]}`}></div>
                                <div className='toast_text'>{notices.content}</div>
                            </div>
                        </div>
                    }
                </div>
            )
        }

        
    }
}
export default ToastBox;

# 4、编写toast.css

.toast {
    /* position: fixed; */
    left: 0;
    top: 0;
    z-index: 999;
    display: flex;
    color: pink;
    flex-direction: column; }
    .toast_bg {
     position: fixed;
     width: 100%;
     height: 100%;
     left: 0;
     top: 0; }
    .toast_box {
     position: relative;
     left: 50%;
     top: 50%;
     width: 100px;
     height: 100px;
     margin: -50px -50px;
     background: rgba(0, 0, 0, 0.65);
     border-radius: .1rem;
     color: #fff; }
    .toast_text {
        position: absolute;
        bottom: 20px;
        left: 0;
        width: 100%;
        text-align: center;
        color: #fff;
    }
    .toast_icon {
     position: relative;
     left: 50%;
     top: 15%;
     margin: -12.5px;
     width: 25px;
     height: 25px; 
    }
    .toast_loading {
     -webkit-animation: loading 1s steps(12, end) infinite;
     animation: loading 1s steps(12, end) infinite;
     background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAENCA8KAgsGDgQMCQUDBwhylaLQAAAL1JREFUOMu9U0kSwyAMK9jsS/T/1zZt2pgEZzq9RBeMZYRGDI/70bO5JptjrOAQVTonIJVK5bW2ma9A7VvpK8OdeQfbZectrDnyU+Oo0b68wGK0muDPdxpOciaizq5pkAgiIPAoew2rBVNYZoM2YHbZDNKz/2Ogam3ff5gMEL8wisfh2KKZiFiGWFkk1B7NSbhNQFy4M2+PghbODNsg7y8THM2njiy8gBgcaEUA9GgNJbxh6fJv+NxiFvYmPAFtCQZNK1qZIAAAAABJRU5ErkJggg==") no-repeat;
     background-size: 100%; }
    .toast_success {
     background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMA8DAQ0GBP4LCggMBwIJBAIttdjAAAAINJREFUOMvdkUsOwyAMBbH5hUCauf9pK1SlohF438x2LPn52f09+8vUfiNb/gighj8FouEjYCUoQDXiBSD7pdcMiK7XC9wCFmlDO3T20Scgx287ne13pwDU89NOJ3g3maCmJDANqIGRtLj8oi1ed1GMdmcB7wXIYX8QdQZJiM5Em3smbyVICDCOrCqSAAAAAElFTkSuQmCC") no-repeat;
     background-size: 100%; }
    .toast_error {
     background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAAMFBMVEUAAAD///////////////////////////////////////////////////////////87TQQwAAAAD3RSTlMA0BDAMODwUKBgsCCAQJClzVPvAAAA0UlEQVQoz2MgErAclv9o44Dgc8b/B4KvBTA+t/3XdgeWivjPG6ACbl8ngNXlp0AN+L8IwtD6DzFm2w+Y3v5sMGW/ACbA9Rms9ZsCTIApH2QR608GhoUKQJ4xA8P8AKCAP5CwF2JgUPwIlPwCFDj/AMRRYJIHCnL8AZkJ1AfkAcUYGNhBpso7MICUgBQw8H4EEv/B5ssDFYA4mAKYWjANfd+Aai3CYZ9BDoM63RDkdEGQ0zE9h+l9zADCDEIGt2/wQEZEwwVepGhgYEdEFGZUEgYAW05XI3jSsVwAAAAASUVORK5CYII=") no-repeat;
     background-size: 100%; }
    .toast_info {
     background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAALVBMVEUAAAD///////////////////////////////////////////////////////+hSKubAAAADnRSTlMA4CCAwKBAMJBg8NAQUNhWlbcAAAC+SURBVCjPYyASsLfse+1cgOBzyr0DgocXYHwmv4dtCkwZck8UoAJZDydA1C2H8NnexUAYR99BjNF6CtMbtwhM+QUACUUhIMH6BKz14QEgafcYSPDIgSxifMkAE2CYJwAk6gQQAozPgURfA0KAA0T6JSAE2ECm7lNACDC9BhLvGGACIA6GAFyLohBEC9xQqLeeQKwFA4i1EIfBAeNzuNMVhSBOx/AcpvcxAwgzCDEDGTMaGHhhEYWIShN4VBIGAPvRT5YzufhUAAAAAElFTkSuQmCC") no-repeat;
     background-size: 100%; }
    
   @-webkit-keyframes loading {
    0% {
     -webkit-transform: rotate3d(0, 0, 1, 0deg);
     transform: rotate3d(0, 0, 1, 0deg); }
    100% {
     -webkit-transform: rotate3d(0, 0, 1, 360deg);
     transform: rotate3d(0, 0, 1, 360deg); } }
    
   @keyframes loading {
    0% {
     -webkit-transform: rotate3d(0, 0, 1, 0deg);
     transform: rotate3d(0, 0, 1, 0deg); }
    100% {
     -webkit-transform: rotate3d(0, 0, 1, 360deg);
     transform: rotate3d(0, 0, 1, 360deg); } 
}

# react-router-dom实现全局路由登录拦截

  1. 建立一个routerMap.js来存储所有的路由信息,定义需要登录拦截的页面(auth);
import { lazy } from 'react';
const Home = lazy(() => import('./Pages/Home'));
const Goods = lazy(() => import('./Pages/Goods'));
const WantBuy = lazy(() => import('./Pages/WantBuy'));
const User = lazy(() => import('./Pages/User'));
const Login = lazy(() => import('./Pages/Login'));
export default [
    { path: "/", name: "Home", component: Home },
    { path: "/home", name: "Home", component: Home },
    { path: "/goods", name: "Goods", component: Goods },
    { path: "/wantbuy", name: "WantBuy", component: WantBuy },
    { path: "/user", name: "User", component: User , auth: true },
    { path: "/login", name: "Login", component: Login }
]
  1. 在App.js里面使用
import React, { Fragment, Suspense } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from "react-router-dom";
import routerMap from './routerMap';

function App(props) {
  let token = sessionStorage.getItem("token");
  return (
    <Router>
      <Fragment>
        <Suspense fallback={<div>{null}</div>}>
          <Switch>
            {
              routerMap.map((item, index) => {
                return (
                  <Route
                    key={index}
                    path={item.path}
                    exact
                    render={props =>
                      (!item.auth ?
                        (<item.component {...props} />)
                        :
                        (token ? <item.component {...props} /> : <Redirect to={{
                          pathname: '/login',
                          state: { from: props.location }
                        }} />)
                      )
                    }
                  />
                )
              })
            }
            // 所有错误路由跳转页面
            <Route component={NotFound} />
          </Switch>
        </Suspense>
      </Fragment>
    </Router>
  )
}
export default App;

以上代码最重要的点是Route组件里面用render属性替换component来渲染页面,根据routerMap.js中的每一条路由信息中auth自定义字段来区分是否需要进行登录拦截,再根据storage里面的token来判断是否是登录状态,然后进行相关的操作.如果已经拦截了就把当前的路由通过Redirectstate来传递到登陆页面,如果登录成功之后就回跳到from.pathname即可

// 登录成功方法
setToken() {
  let token = sessionStrorge("token");
  if(!token) return;
  let RedirectUrl = this.props.location.state ? this.props.location.state.from.pathname : '/';
  this.props.history.push(RedirectUrl)
}