demo需求
首页是登录页,路由是登录页和注册页和忘记密码页面,3页面共享redux的数据,3页面通过路由(react-router-config)切换。
首页重定向到登录页:
拿到全局数据TOM
注册页:拿到全局数据TOM
文件结构
在routes里面写路由文件
import React from 'react';
import { Redirect } from 'react-router-dom';
import Frame from './pages/login/Frame';
import Sign from './pages/login/Sign';
import Forget from './pages/login/Forget';
import Login from './pages/login/Login';
export const Routes = [
{
path: '/login',
component: Frame,
exact: false,
routes: [
{
path: '/login',
exact: true,
component: Login,
},
{
path: '/login/sign',
exact: true,
component: Sign,
},
{
path: '/login/forget',
exact: true,
component: Forget,
},
],
},
{
path: '*',
render: () => <Redirect to="/login"></Redirect>,
},
];
react-router-config使用
先安装react-router-dom和react-router-config
yarn add react-router-dom react-router-config
import { renderRoutes } from 'react-router-config';
使用场景:
react-router-config用于静态路由配置,属于react-router的一个插件,主要用于集中管理路由配置
在src\pages\login\Frame.jsx这里是嵌套路由触发的组件,引入react-router-config
import React from 'react';
import { renderRoutes } from 'react-router-config';
const Frame = ({route}) => {//写法2 const Frame = (props)
return (
<div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
backgroundColor: 'gray',
height: 100,
padding: 25,
boxSizing: 'border-box'
}}>
<div>logo</div>
<div>顶部导航栏</div>
<div>用户登录</div>
</div>
{/* 上面和下面是页头和页脚是固定的。这里负责渲染子路由的页面 */}
<div>{renderRoutes(route.routes)}</div> //写法2 props.route.routes
<div
style={{
position: 'fixed',
bottom: 0,
textAlign: 'center',
height: 100,
paddingTop: 30,
boxSizing: 'border-box',
backgroundColor: 'grey',
width: '100%'
}}>
页脚footer企业信息
</div>
</div >
);
}
export default Frame;
Forget.jsx
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { GlobalContext } from '../../store';
const Forget = () => {
const history = useHistory()
const { state } = useContext(GlobalContext)
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div>
<h1>忘记密码页面</h1>
<div>
账号 <input type="text" />
</div>
<div>
密码 <input type="text" />
</div>
<div>
<button
onClick={() => {
history.goBack()
}}
>返回</button>
</div>
<h2>
{
state.username && (
<div>
从全局拿到登录用户名 {state.username}
</div>
)
}
</h2>
</div>
</div>
);
}
export default Forget;
Frame.jsx
import React from 'react';
import { renderRoutes } from 'react-router-config';
const Frame = ({route}) => {
return (
<div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
backgroundColor: 'gray',
height: 100,
padding: 25,
boxSizing: 'border-box'
}}>
<div>logo</div>
<div>顶部导航栏</div>
<div>用户登录</div>
</div>
{/* 上面和下面是页头和页脚是固定的。这里负责渲染子路由的页面 */}
<div>{renderRoutes(route.routes)}</div>
<div
style={{
position: 'fixed',
bottom: 0,
textAlign: 'center',
height: 100,
paddingTop: 30,
boxSizing: 'border-box',
backgroundColor: 'grey',
width: '100%'
}}>
页脚footer企业信息
</div>
</div >
);
}
export default Frame;
Login.jsx
import React, { useContext, useState } from 'react';
import { Link } from 'react-router-dom';
import { GlobalContext } from '../../store';
const Login = () => {
const { state, dispatch } = useContext(GlobalContext)
const [username, setname] = useState('')
const [password, setpwd] = useState('')
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div>
<h1>登录页面</h1>
<div>
账号 <input type="text" value={username} onInput={e => setname(e.target.value)} />
</div>
<div>
密码 <input type="text" value={password} onInput={e => setpwd(e.target.value)} />
</div>
<div>
<button
onClick={() => {
dispatch({
type: 'login',
username,
password
})
}}
>登录</button>
</div>
<div>
<Link to="/login/sign">注册</Link>
<Link to="/login/forget" style={{ marginLeft: 20 }}>忘记密码</Link>
</div>
<h2>
{
state.username && (
<div>
登录成功, 欢迎全局状态来的 {state.username}
</div>
)
}
</h2>
</div>
</div>
);
}
export default Login;
Sign.jsx
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { GlobalContext } from '../../store';
const Sign = () => {
const history = useHistory()
const { state } = useContext(GlobalContext)
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div>
<h1>注册页面</h1>
<div>
账号 <input type="text" />
</div>
<div>
密码 <input type="text" />
</div>
<div>
<button
onClick={() => {
history.goBack()
}}
>返回</button>
<h2>
{
state.username && (
<div>
从全局拿到登录用户名 {state.username}
</div>
)
}
</h2>
</div>
</div>
</div>
);
}
export default Sign;
APP.js
import React, { useReducer } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import { Routes } from './routes';
import { GlobalContext, initState, reducer } from './store';
function App() {
const [state, dispatch] = useReducer(reducer, initState)
return (
<GlobalContext.Provider value={{ state, dispatch }}>
<BrowserRouter>
{renderRoutes(Routes)}
</BrowserRouter>
</GlobalContext.Provider>
);
}
export default App;
routes
import React from 'react';
import { Redirect } from 'react-router-dom';
import Frame from './pages/login/Frame';
import Sign from './pages/login/Sign';
import Forget from './pages/login/Forget';
import Login from './pages/login/Login';
export const Routes = [
{
path: '/login',
component: Frame,
exact: false,
routes: [
{
path: '/login',
exact: true,
component: Login,
},
{
path: '/login/sign',
exact: true,
component: Sign,
},
{
path: '/login/forget',
exact: true,
component: Forget,
},
],
},
{
path: '*',
render: () => <Redirect to="/login"></Redirect>,
},
];
stoe.js
import { createContext } from 'react';
export const GlobalContext = createContext();
export const initState = {
username: '',
password: ''
}
export function reducer(state, actions) {
const {username,password} = actions
if (actions.type === 'login') {
return {
...state,
username,
password,
}
}
return state
}
HOOKS延伸
React.createContext()
为什么出现这个api?
简单说,就是如果context的值有更新时,没办法保证所有子节点一定能更新。
为什么?因为在老的Context中由上而下的“触发链”有可能被shouldComponentUpdate打断。
原理也很简单,本质就是要实现跨过中间components的通信,这里用pub-sub模式实现一个简单的createContext
const emitter = {
listeners: [],
on: fn => {
emitter.listeners.push(fn);
},
off: fn => {
emitter.listeners.splice(emitter.listener.findIndex(fn), 1);
},
emit: value => {
emitter.listeners.forEach(fn => fn(value));
}
};
function createContext(defaultValue) {
class Provider extends React.PureComponent {
componentDidUpdate() {
emitter.emit(this.props.value);
}
componentDidMount() {
emitter.emit(this.props.value);
}
render() {
return this.props.children;
}
}
class Consumer extends React.PureComponent {
constructor(props) {
super(props);
this.state = { value: defaultValue };
emitter.on(value => {
console.log(value);
this.setState({ value });
});
}
render() {
return this.props.children(this.state.value);
}
}
return { Provider, Consumer };
}
解释一下:
1. 创建了一个emitter
2. 在Provider里的`componentDidUpdate`和`componentDidMount`中触发`emmiter.emit`
3. 在Consumer里注册监听,一旦有value变化,便触发`this.setState`,自然会触发re-render
所以这里我们能看出来,只要`Provider`的value有变化,就一定会触发`Consumer`的state变化。在老的`Context`中被`shouldComponentUpdate`打断的“触发链”又被重新接上了。
使用 useReducer 减少 Context 的复杂程度
直接使用父组件 state 带来的性能问题
注意看上面的动图,在点击子组件的 【number + step】 按钮的时候,虽然 count 的值没有发生任何变化,但是一直触发 re-render,即使子组件是通过 React.memo 包装过的。
出现这个问题原因是 React.memo 只会对 props 进行浅比较,而通过 Context 我们直接将 state 注入到了组件内部,因此 state 的变化必然会触发 re-render,整个 state 变化是绕过了 memo。
使用 useMemo() 解决 state Context 透传的性能问题
既然 React.memo() 无法拦截注入到 Context 的 state 的变化,那就需要我们在组件内部进行更细粒度的性能优化,这个时候可以使用 useMemo()
1、使用 useMemo 优化子组件渲染
下面是对子组件的改造,去掉了 React.memo,在 return 内部通过 useMemo() 包装,并且声明了所有依赖项:(包括:step/number/count/dispatch)
import React, { useContext, useMemo } from 'react';
import { MyContext } from './context-manager';
export default (props = {}) => {
const { state, dispatch } = useContext(MyContext);
return useMemo(() => {
console.log('[Child] RE-RENDER');
return (
<div>
<p>step is : {state.step}</p>
<p>number is : {state.number}</p>
<p>count is : {state.count}</p>
<hr />
<div>
<button onClick={() => { dispatch({ type: 'stepInc' }) }}>step ++</button>
<button onClick={() => { dispatch({ type: 'numberInc' }) }}>number ++</button>
<button onClick={() => { dispatch({ type: 'count' }) }}>number + step</button>
</div>
</div>
)
}, [state.count, state.number, state.step, dispatch]);
}