# React技巧
# React实现全局组件的方式
# 简单实现一个Loading组件
需求分析:
- Loading 不需要同页面一起调用,而是根据需要被随时调用
- 调用 Loading 的 show() 和 hide() 全局方法
# 1. 创建文件
创建Loading
文件夹,然后创建Loading.js
,index.js
,Loading.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实现全局路由登录拦截
- 建立一个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 }
]
- 在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
来判断是否是登录状态,然后进行相关的操作.如果已经拦截了就把当前的路由通过Redirect
的state
来传递到登陆页面,如果登录成功之后就回跳到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)
}