前言
本篇文章记录的是上手react并做好一个React项目的大概知识点,比如antd、transition、redux、react-redux、middleware、reducer、router、hooks等等。
antd
一套完整的解决方案的生态。
https://ant.design/components/layout-cn/
transition
CSSTransition
js
import React, { PureComponent } from 'react';
import './CSSTransition.css';
import { CSSTransition } from 'react-transition-group';
import { Card, Avatar } from 'antd';
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons';
const { Meta } = Card;
export default class CSSTransitionDemo extends PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: true
}
}
render() {
const { isShow } = this.state;
return (
<div>
<button onClick={e => { this.setState({ isShow: !isShow }) }}>显示/隐藏</button>
<CSSTransition in={isShow}
classNames="card"
timeout={5000}
unmountOnExit={true}
appear
onEnter={el => console.log("开始进入")}
onEntering={el => console.log("正在进入")}
onEntered={el => console.log("进入完成")}
onExit={el => console.log("开始退出")}
onExiting={el => console.log("退出状态")}
onExited={el => console.log("退出完成")}
>
<Card
style={{ width: 300 }}
cover={
<img
alt="example"
src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png"
/>
}
actions={[
<SettingOutlined key="setting" />,
<EditOutlined key="edit" />,
<EllipsisOutlined key="ellipsis" />,
]}
>
<Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title="Card title"
description="This is the description"
/>
</Card>
</CSSTransition>
</div>
)
}
}
css
/* enter:入场动画执行前的时候 第一个时刻。appear:第一次入场 */
.card-enter, .card-appear {
opacity: 0;
transform: scale(.6);
}
/* enter-active:入场动画在执行的时候 第二个时刻。appear-active:第一次入场中 */
.card-enter-active, .card-appear-active {
opacity: 1;
transform: scale(1);
transition: opacity 300ms, transform 300ms;
}
/* enter-done:入场动画完全执行完毕后 第三个时刻。appear-done:第一次入场结束 */
.card-enter-done, .card-appear-done {
/* background-color: red; */
}
/* exit:出场动画执行前的时候 第四个时刻 */
.card-exit {
opacity: 1;
transform: scale(1);
}
/* exit-active:出场动画在执行的时候 第五个时刻 */
.card-exit-active {
opacity: 0;
transform: scale(.6);
transition: opacity 300ms, transform 300ms;
}
/* exit-done:出场动画完全执行完毕的时候 第六个时刻 */
.card-exit-done {
opacity: 0;
}
SwitchTransition
js
import React, { PureComponent } from 'react';
import "./SwitchTransition.css";
import { SwitchTransition, CSSTransition } from 'react-transition-group';
export default class SwitchTransitionDemo extends PureComponent {
constructor(props) {
super(props);
this.state = {
isOn: true
}
}
render() {
const { isOn } = this.state;
return (
<div>
{/* 有两种模式:先移除再添加 out-in,先添加再移除 in-out */}
<SwitchTransition mode="out-in">
{/* 绑定一个key,SwitchTransition会移除组件,如果key是固定,那就会不停的移除 */}
<CSSTransition key={isOn ? "on" : "off"}
classNames="btn"
timeout={1000}>
<button onClick={e => this.setState({ isOn: !isOn })}>
{isOn ? "开" : "关"}
</button>
</CSSTransition>
</SwitchTransition>
</div>
)
}
}
css
/* enter:入场动画执行前的时候 第一个时刻 */
.btn-enter {
opacity: 0;
transform: translateX(100%);
}
/* enter-active:入场动画在执行的时候 第二个时刻。*/
.btn-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 1000ms, transform 1000ms;
}
.btn-enter-done {
}
/* exit:出场动画执行前的时候 第四个时刻 */
.btn-exit {
opacity: 1;
transform: translateX(0);
}
/* exit-active:出场动画在执行的时候 第五个时刻 */
.btn-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: opacity 1000ms, transform 1000ms;
}
.btn-exit-done {
}
TransitionGroup
js
import React, { PureComponent } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './TransitionGroup.css';
export default class TransitionGroupDemo extends PureComponent {
constructor(props) {
super(props);
this.state = {
names: ["m2", "aiyou", "diao"]
}
}
render() {
return (
<div>
<TransitionGroup>
{
this.state.names.map((item, index) => {
return (
<CSSTransition key={item}
timeout={500}
classNames="item">
<div>
{item}
<button onClick={e => this.removeItem(index)}>移除</button>
</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={e => this.addName()}>添加</button>
</div>
)
}
addName() {
this.setState({
names: [...this.state.names, "m2"]
})
}
removeItem(index) {
// index indey indez
this.setState({
names: this.state.names.filter((item, indey) => index !== indey)
})
}
}
css
/* enter:入场动画执行前的时候 第一个时刻 */
.item-enter {
opacity: 0;
transform: scale(.6);
}
/* enter-active:入场动画在执行的时候 第二个时刻。*/
.item-enter-active {
opacity: 1;
transform: scale(1);
transition: opacity 300ms, transform 300ms;
}
/* enter-done:入场动画完全执行完毕后 第三个时刻。*/
.item-enter-done {
color: red;
}
/* exit:出场动画执行前的时候 第四个时刻 */
.item-exit {
opacity: 1;
transform: scale(1);
}
/* exit-active:出场动画在执行的时候 第五个时刻 */
.item-exit-active {
opacity: 0;
transform: scale(.6);
transition: opacity 300ms, transform 300ms;
}
/* exit-done:出场动画完全执行完毕的时候 第六个时刻 */
.item-exit-done {
opacity: 0;
}
redux
ui/client:redux.createStore(reducer)
store:store.dispach、store.subscribe、store.getState
reducer:根据不同commander返回新的state
commander:(...args)=>({type: commandType, ...args})
constants:定义于commandType的常量
react-redux
通过Provider标签,设置store属性。
简单的通过store取值以及监听store中的state
import React, { PureComponent } from 'react';
import store from '../store';
import {
addAction
} from '../store/actionCreators'
export default class Home extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
this.unsubscribue = store.subscribe(() => {
this.setState({
counter: store.getState().counter
})
})
}
componentWillUnmount() {
this.unsubscribue();
}
render() {
return (
<div>
<h1>Home</h1>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.addNumber(5)}>+5</button>
</div>
)
}
increment() {
store.dispatch(addAction(1));
}
addNumber(num) {
store.dispatch(addAction(num));
}
}
使用context来实现redux中的connect
connnect.jsx
import React, { PureComponent } from "react";
export const StoreContext = React.createContext();
export function connect(mapStateToProps, mapDispachToProp) {
return function enhanceHOC(WrappedComponent) {
class EnhanceComponent extends PureComponent {
constructor(props, context) {
super(props, context);
this.state = {
storeState: mapStateToProps(context.getState())
}
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
storeState: mapStateToProps(this.context.getState())
})
})
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return <WrappedComponent {...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispachToProp(this.context.dispatch)} />
}
}
EnhanceComponent.contextType = StoreContext;
return EnhanceComponent;
}
}
home.jsx
import React, { PureComponent } from 'react';
import {connect} from '../utils/connect';
import {
incAction,
addAction
} from '../store/actionCreators'
class Home extends PureComponent {
render() {
return (
<div>
<h1>Home</h1>
<h2>当前计数: {this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
increment() {
dispatch(incAction());
},
addNumber(num) {
dispatch(addAction(num));
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);
函数式的组件的写法
import React from 'react';
import { connect } from '../utils/connect';
import {
decAction,
subAction
} from "../store/actionCreators";
function About(props) {
return (
<div>
<hr />
<h1>About</h1>
<h2>当前计数: {props.counter}</h2>
<button onClick={e => props.decrement()}>-1</button>
<button onClick={e => props.subNumber(5)}>-5</button>
</div>
)
}
const mapStateToProps = state => {
return {
counter: state.counter
}
};
const mapDispatchToProps = dispatch => {
return {
decrement: function() {
dispatch(decAction());
},
subNumber: function(num) {
dispatch(subAction(num))
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
react-redux的connect
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import {
incAction,
addAction,
changeBannersAction,
changeRecommendAction
} from '../store/actionCreators'
class Home extends PureComponent {
componentDidMount() {
axios({
url: "http://127.0.0.1:3000/multidata.json",
}).then(res => {
const {data, returnCode, success} = res.data;
// 从上面自己实现的connect中可以看到,mapStateToProps和mapDispatchToProps中的成员都会平铺赋值给当前组件
this.props.changeBanners(data.banner.list);
this.props.changeRecommends(data.recommend.list);
})
}
render() {
return (
<div>
<h1>Home</h1>
<h2>当前计数: {this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
increment() {
dispatch(incAction());
},
addNumber(num) {
dispatch(addAction(num));
},
changeBanners(banners) {
dispatch(changeBannersAction(banners));
},
changeRecommends(recommends) {
dispatch(changeRecommendAction(recommends));
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);
函数式的组件的写法
import React from 'react';
import { connect } from 'react-redux';
import {
decAction,
subAction
} from "../store/actionCreators";
function About(props) {
console.log("页面重新渲染");
return (
<div>
<hr />
<h1>About</h1>
<h2>当前计数: {props.counter}</h2>
<button onClick={e => props.decrement()}>-1</button>
<button onClick={e => props.subNumber(5)}>-5</button>
<h1>Banner</h1>
<ul>
{
props.banners.map((item, index) => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
<h1>Recommend</h1>
<ul>
{
props.recommends.map((item, index) => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
</div>
)
}
const mapStateToProps = state => {
return {
banners: state.banners,
recommends: state.recommends
}
};
const mapDispatchToProps = dispatch => {
return {
decrement: function () {
dispatch(decAction());
},
subNumber: function (num) {
dispatch(subAction(num))
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
函数式的组件的写法外升级版,不使用connect
上面函数式组件的写法会随着props的变化而进行重新render,性能不好。所以需要使用 react-redux 中的useSelector来监听store中状态的变化,而不再是通过connect之后的props了。
import React from 'react';
import { connect, useSelector, shallowEqual } from 'react-redux';
import {
decAction,
subAction
} from "../store/actionCreators";
function About(props) {
const { banners, recommends, counter } = useSelector(state => ({
banners: state.banners,
recommends: state.recommends,
counter: state.counter
}), shallowEqual);
console.log("About页面重新渲染了");
return (
<div>
<hr />
<h1>About</h1>
<h2>当前计数: {counter}</h2>
<h1>Banner</h1>
<ul>
{
banners.map((item, index) => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
<h1>Recommend</h1>
<ul>
{
recommends.map((item, index) => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
</div>
)
}
export default About;
使用redux-thunk
使用redux-thunk 实际上是dispatch的操作往后延迟了一步,也就是让dispatch支持传递一个函数,然后你可以在函数中去一些异步的操作了,比如发请求处理数据之类的。最终再去调用真正的dispatch方法去变更state。
它是在store中进行了redux-thunk的中间件使用,在创建store的时候将这个redux-thunk中间件添加进去了。
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducer.js';
// composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) : compose
// 应用中间件
const storeEnhancer = applyMiddleware(thunkMiddleware);
const store = createStore(reducer, composeEnhancers(storeEnhancer));
export default store;
actionCreators.js
export const changeBannersAction = (banners) => ({
type: CHANGE_BANNERS,
banners
});
export const changeRecommendAction = (recommends) => ({
type: CHANGE_RECOMMEND,
recommends
});
// redux-thunk中定义的action函数,
export const getHomeMultidataAction = (dispatch, getState) => {
axios({
url: "http://127.0.0.1:3000/multidata.json",
}).then(res => {
const data = res.data.data;
dispatch(changeBannersAction(data.banner.list));
dispatch(changeRecommendAction(data.recommend.list));
})
}
home.jsx
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import {
incAction,
addAction,
getHomeMultidataAction
} from '../store/actionCreators'
class Home extends PureComponent {
componentDidMount() {
this.props.getHomeMultidata();
}
render() {
return (
<div>
<h1>Home</h1>
<h2>当前计数: {this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
increment() {
dispatch(incAction());
},
addNumber(num) {
dispatch(addAction(num));
},
getHomeMultidata() {
dispatch(getHomeMultidataAction);
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);
使用 redux-saga
redux-saga是基于生成器实现的,可能用起来有点晦涩难懂,但用熟悉了就OK。中文文档:https://redux-saga-in-chinese.js.org/
saga也是重写了dispatch,同时它在里面插入了你定义的一些生成器的函数,调用dispatch时会去执行生成器函数,然后由生成器函数去执行action中的操作,最终通过reducer去修改的state。
中间件可以混合使用,比如redux-thunk和redux-saga可以在一起使用。
最常用的中间件就是做拦截器,拦截到后做判断,判断ok就处理并返回处理结果。如果判断不ok,就看看后面还有其它中间件,那些中间件继续拦截,继续判断,继续处理并返回处理结果。
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import saga from './saga';
import reducer from './reducer.js';
// composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) : compose
// 应用中间件
const sagaMiddleware = createSagaMiddleware();
// 先thunk 再 saga
const storeEnhancer = applyMiddleware(thunkMiddleware, sagaMiddleware);
const store = createStore(reducer, composeEnhancers(storeEnhancer));
sagaMiddleware.run(saga);
export default store;
saga.js
import axios from 'axios';
import { takeEvery, put, all, takeLatest } from 'redux-saga/effects';
import {
FETCH_HOME_MULTIDATA, ADD_NUMBER
} from './constants';
import {
changeBannersAction,
changeRecommendAction
} from './actionCreators';
function* fetchHomeMultidata(action) {
const res = yield axios.get("http://127.0.0.1:3000/multidata.json");
const banners = res.data.data.banner.list;
const recommends = res.data.data.recommend.list;
// 单个单个执行
// yield put(changeBannersAction(banners));
// yield put(changeRecommendAction(recommends));
// 数据中的一起执行
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendAction(recommends))
])
}
function* mySaga() {
// takeLatest: 依次只能监听一个对应的action
// takeEvery: 每一个都会被执行
yield all([
takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata),
// takeLatest(ADD_NUMBER, fetchHomeMultidata),
]);
}
export default mySaga;
actionCreators.js
// 轮播图的action
export const changeBannersAction = (banners) => ({
type: CHANGE_BANNERS,
banners
});
// 推荐的action
export const changeRecommendAction = (recommends) => ({
type: CHANGE_RECOMMEND,
recommends
});
// redux-saga拦截的action
export const fetchHomeMultidataAction = {
type: FETCH_HOME_MULTIDATA
}
home.jsx
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import {
incAction,
addAction,
fetchHomeMultidataAction
} from '../store/actionCreators'
class Home extends PureComponent {
componentDidMount() {
this.props.getHomeMultidata();
}
render() {
return (
<div>
<h1>Home</h1>
<h2>当前计数: {this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
increment() {
dispatch(incAction());
},
addNumber(num) {
dispatch(addAction(num));
},
getHomeMultidata() {
dispatch(fetchHomeMultidataAction);
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);
middleware
redux中间件都是通过劫持store的dispatch来实现的。中间件一般都是洋葱圈模型,就像是函数调用栈以及递归一样的,也就是说中间件可以混合起来使用的,比如redux-thunk和redux-saga可以一起使用,互不干扰的,最终还是调用redux中的dispatch函数,然后通过reducer中操作来改变state的。
简单的登录劫持
import store from './store';
function patchLogging(store) {
const next = store.dispatch;
function dispatchAndLogging(action) {
console.log("dispatch前---dispatching action:", action);
next(action);
console.log("dispatch后---new state:", store.getState());
}
// store.dispatch = dispatchAndLogging;
return dispatchAndLogging;
}
patchLogging(store);
thunk的中间件
import store from './store';
// 封装patchThunk的功能
function patchThunk(store) {
const next = store.dispatch;
function dispatchAndThunk(action) {
if (typeof action === "function") {
action(store.dispatch, store.getState)
} else {
next(action);
}
}
// store.dispatch = dispatchAndThunk;
return dispatchAndThunk;
}
patchThunk(store);
实现applyMiddlewares
function applyMiddlewares(...middlewares) {
// const newMiddleware = [...middlewares];
middlewares.forEach(middleware => {
store.dispatch = middleware(store);
})
}
applyMiddlewares(patchLogging, patchThunk);
console.log('store.dispatch', store.dispatch)
拆分与合并reducer
当reducer中操作state过多时,可以通过拆分reducer来解决reducer过多的问题。
计数器的reducer
import {
ADD_NUMBER,
SUB_NUMBER,
INCREMENT,
DECREMENT
} from './constants.js';
// 拆分后的counterReducer
const initialCounterState = {
counter: 0
}
function counterReducer(state = initialCounterState, action) {
switch (action.type) {
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
case SUB_NUMBER:
return { ...state, counter: state.counter - action.num };
case INCREMENT:
return { ...state, counter: state.counter + 1 };
case DECREMENT:
return { ...state, counter: state.counter - 1 };
default:
return state;
}
}
export default counterReducer;
首页的reducer
import {
CHANGE_BANNERS,
CHANGE_RECOMMEND
} from './constants.js';
// 拆分后的homeReducer
const initialHomeState = {
banners: [],
recommends: []
}
function homeReducer(state = initialHomeState, action) {
switch (action.type) {
case CHANGE_BANNERS:
return { ...state, banners: action.banners };
case CHANGE_RECOMMEND:
return { ...state, recommends: action.recommends };
default:
return state;
}
}
export default homeReducer;
合并counter 和 home 的reducer
import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';
import { combineReducers } from 'redux';
// combineReducers 大概做的就是这样的事情,返回一个新的函数
// function reducer(state = {}, action) {
// return {
// counterInfo: counterReducer(state.counterInfo, action),
// homeInfo: homeReducer(state.homeInfo, action)
// }
// }
//reducer是一个function
const reducer = combineReducers({
counterInfo: counterReducer,
homeInfo: homeReducer
});
export default reducer;
使用store中的state时发生了变化
import React from 'react';
import { connect } from 'react-redux';
import {
decAction,
subAction
} from '../store/counter/actionCreators';
function About(props) {
return (
<div>
<hr />
<h1>About</h1>
<h2>当前计数: {props.counter}</h2>
<button onClick={e => props.decrement()}>-1</button>
<button onClick={e => props.subNumber(5)}>-5</button>
<h1>Banner</h1>
<ul>
{
props.banners.map((item, index) => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
<h1>Recommend</h1>
<ul>
{
props.recommends.map((item, index) => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
</div>
)
}
const mapStateToProps = state => {
return {
// 多了一层名称,也就是你拆分后在总的reducer目录下使用的子reducer的别名
counter: state.counterInfo.counter,
banners: state.homeInfo.banners,
recommends: state.homeInfo.recommends
}
};
const mapDispatchToProps = dispatch => {
return {
decrement: function () {
dispatch(decAction());
},
subNumber: function (num) {
dispatch(subAction(num))
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
router
本笔记在记录的时候,用的是react-router5,并不是6,所以可能有点不同,但使用起来大致相同,最新的官方文档是我会贴出来。
官网:https://reactrouter.com/docs/en/v6/getting-started/tutorial
BrowserRouter:history 路由
HashRouter:hash 路由
createMemoryHistory: 内存路由
Link:链接
Route:路由容器
NavLink:支持使用class的链接
Switch:路由容器切换器,react-router6是Routes
withRouter:高阶组件,包裹组件,给这个组件添加了location、history、match、staticContext属性,这样当前组件可以通过this.props.history来切换路由
renderRoutes:将一个路由配置转成路由容器的集合
Redirect:跳转
属性
exact:精准匹配
path:路径
component:组件
简单的路由使用
import React, { PureComponent } from 'react';
import {
Link,
Route,
NavLink,
Switch,
} from 'react-router-dom';
import Home from './pages/home';
import About from './pages/about';
import Profile from './pages/profile';
import User from './pages/user';
import NoMatch from './pages/noMatch';
import Login from './pages/login';
import Product from './pages/product';
import Detail from './pages/detail';
import Detail2 from './pages/detail2';
import Detail3 from './pages/detail3';
class App extends PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Link to="/" >首页</Link>
<Link to="/about" >关于</Link>
<Link to="/profile" >我的</Link>
{/* NavLink的使用 */}
<NavLink exact to="/" activeStyle={{color: "red", fontSize: "30px"}}>首页</NavLink>
<NavLink to="/about" activeStyle={{color: "red", fontSize: "30px"}}>关于</NavLink>
<NavLink to="/profile" activeStyle={{color: "red", fontSize: "30px"}}>我的</NavLink>
{/* Switch组件的作用: 路径和组件之间的映射关系 */}
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/profile" component={Profile} />
<Route path="/:id" component={User} />
<Route path="/user" component={User} />
<Route path="/login" component={Login} />
<Route path="/product" component={Product} />
<Route path="/detail/:id" component={Detail} />
<Route path="/detail2" component={Detail2} />
<Route path="/detail3" component={Detail3} />
<Route component={NoMatch} />
</Switch>
</div>
)
}
}
export default App;
路由传参和路由方法跳转
import React, { PureComponent } from 'react';
import {
NavLink,
Switch,
Route,
withRouter
} from 'react-router-dom';
import Home from './pages/home';
import About from './pages/about';
import Profile from './pages/profile';
import User from './pages/user';
import NoMatch from './pages/noMatch';
import Login from './pages/login';
import Product from './pages/product';
import Detail from './pages/detail';
import Detail2 from './pages/detail2';
import Detail3 from './pages/detail3';
class App extends PureComponent {
constructor(props) {
super(props);
}
render() {
const id = "123";
const info = {name: "m2", age: 18, height: 1.88};
return (
<div>
<NavLink exact to="/" activeClassName="link-active">首页</NavLink>
<NavLink to="/about" activeClassName="link-active">关于</NavLink>
<NavLink to="/profile" activeClassName="link-active">我的</NavLink>
<NavLink to="/abc" activeClassName="link-active">abc</NavLink>
<NavLink to="/user" activeClassName="link-active">用户</NavLink>
{/* 取值:this.props.match.params.id */}
<NavLink to={`/detail/${id}`} activeClassName="link-active">详情</NavLink>
{/* 取值:this.props.location.search */}
<NavLink to={`/detail2?name=m2&age=18`} activeClassName="link-active">详情2</NavLink>
{/* 取值:this.props.location */}
<NavLink to={{
pathname: "/detail3",
search: "name=abc",
state: info
}}
activeClassName="link-active">
详情3
</NavLink>
<button onClick={e => this.jumpToProduct()}>商品</button>
{/* 2.Switch组件的作用: 路径和组件之间的映射关系 */}
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/profile" component={Profile} />
<Route path="/:id" component={User} />
<Route path="/user" component={User} />
<Route path="/login" component={Login} />
<Route path="/product" component={Product} />
<Route path="/detail/:id" component={Detail} />
<Route path="/detail2" component={Detail2} />
<Route path="/detail3" component={Detail3} />
<Route component={NoMatch} />
</Switch>
</div>
)
}
jumpToProduct() {
console.log('this', this)
console.log('this.props', this.props)
console.log('this.props.history', this.props.history)
this.props.history.push("/product");
}
}
export default withRouter(App);
router配置文件
import Home from '../pages/home';
import About, { AboutHisotry, AboutCulture, AboutContact, AboutJoin } from '../pages/about';
import Profile from '../pages/profile';
import User from '../pages/user';
const routes = [
{
path: "/",
exact: true,
component: Home
},
{
path: "/about",
component: About,
routes: [
{
path: "/about",
exact: true,
component: AboutHisotry
},
{
path: "/about/culture",
component: AboutCulture
},
{
path: "/about/contact",
component: AboutContact
},
{
path: "/about/join",
component: AboutJoin
},
]
},
{
path: "/profile",
component: Profile
},
{
path: "/user",
component: User
}
]
export default routes;
渲染配置为路由容器
import React, { PureComponent } from 'react';
import { renderRoutes } from 'react-router-config';
import routes from './router';
import {
Link,
withRouter
} from 'react-router-dom';
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
<Link to="/" >首页</Link>
<Link to="/about" >关于</Link>
<Link to="/profile" >我的</Link>
{renderRoutes(routes)}
</div>
)
}
}
export default withRouter(App);
在子页面中渲染配置为路由容器
import React, { PureComponent } from 'react'
import { NavLink, Switch, Route } from 'react-router-dom';
import { renderRoutes, matchRoutes } from 'react-router-config';
export function AboutHisotry(props) {
return <h2>AboutHisotry</h2>
}
export function AboutCulture(props) {
return <h2>AboutCulture</h2>
}
export function AboutContact(props) {
return <h2>AboutContact</h2>
}
export function AboutJoin(props) {
return <h2>AboutJoin</h2>
}
export default class About extends PureComponent {
render() {
console.log(this.props.route);
const branch = matchRoutes(this.props.route.routes, "/about");
console.log(branch);
return (
<div>
<NavLink exact to="/about" activeClassName="about-active">AboutHisotry</NavLink>
<NavLink exact to="/about/culture" activeClassName="about-active">AboutCulture</NavLink>
<NavLink exact to="/about/contact" activeClassName="about-active">AboutContact</NavLink>
<button onClick={e => this.jumpToJoin()}>AboutJoin</button>
{/* <Switch>
<Route exact path="/about" component={AboutHisotry}/>
<Route path="/about/culture" component={AboutCulture}/>
<Route path="/about/contact" component={AboutContact}/>
<Route path="/about/join" component={AboutJoin}/>
</Switch> */}
{/* 渲染当前页面的子路由容器,这里的routes是 router配置文件中当前About路由的子routes配置 */}
{renderRoutes(this.props.route.routes)}
</div>
)
}
jumpToJoin() {
// console.log(this.props.history);
// console.log(this.props.location);
// console.log(this.props.match);
this.props.history.push("/about/join");
}
}
hooks
react的hooks降低了理解成本,也让react组件更像是函数。它没有什么魔法,就是数组,还是两个数组,一个存所有state,一个存所有的state的setter。用到了闭包,容易发生内存泄露。
class语法虽然难以理解,都是代码逻辑比较清晰,毕竟是面向对象,官方并没有废除class。
hooks往往作为那种单节点组件,比如几十个表单域。最后使用class来写一个整个表单的数据流的入口。
简单使用
class组件
import React, { PureComponent } from 'react'
export default class CounterClass extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 100
}
}
render() {
console.log("class counter 的渲染");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.decrement()}>-1</button>
</div>
)
}
increment() {
this.setState({counter: this.state.counter + 1})
}
decrement() {
this.setState({counter: this.state.counter - 1})
}
}
hook组件
import React, { useState } from 'react';
export default function CounterHook() {
/**
* Hook: useState
* 参数: 作用是给创建出来的状态一个默认值
* 返回值: 数组[当前state的值, 使用的一个函数设置新的值]
*/
const arr = useState(0);
const state = arr[0];
const setState = arr[1];
return (
<div>
<h2>当前计数: {state}</h2>
<button onClick={e => setState(state + 1)}>+1</button>
<button onClick={e => setState(state - 1)}>-1</button>
</div>
)
}
解决多次set合并的问题
import React, {useState} from 'react';
export default function CounterHook() {
const [count, setCount] = useState(() => 1001);
console.log("CounterHook的渲染");
function handleBtnClick() {
// setCount(count + 1001);
// setCount(count + 1001);
setCount((prevCount) => prevCount + 1001);
setCount((prevCount) => prevCount + 1001);
}
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<button onClick={e => setCount((prevCount) => prevCount + 1001)}>+1001</button>
<button onClick={handleBtnClick}>+1001</button>
<button onClick={e => setCount(count - 1)}>-1</button>
</div>
)
}
useState
多状态
import React, { useState } from 'react';
export default function MultiHookState() {
const [count, setCount] = useState(0);
const [age, setAge] = useState(18);
const [friends, setFriends] = useState(["m2", "diao"]);
return (
<div>
<h2>当前计数: {count}</h2>
<h2>我的年龄: {age}</h2>
<ul>
{
friends.map((item, index) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
)
}
复杂状态
import React, { useState } from 'react'
export default function ComplexHookState() {
const [friends, setFrineds] = useState(["aiyou", "m2"]);
const [students, setStudents] = useState([
{ id: 28, name: "aiyou", age: 18 },
{ id: 29, name: "diao", age: 23 },
{ id: 30, name: "m2", age: 25 },
])
// 和setState一样,不要直接改,拷贝一份,从新的里面改
function addFriend() {
friends.push("lxx");
setFrineds(friends);
}
function incrementAgeWithIndex(index) {
const newStudents = [...students];
newStudents[index].age += 1;
setStudents(newStudents);
}
return (
<div>
<h2>好友列表:</h2>
<ul>
{
friends.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
<button onClick={e => setFrineds([...friends, "tom"])}>添加朋友</button>
{/* 错误的做法 */}
<button onClick={addFriend}>添加朋友</button>
<h2>学生列表</h2>
<ul>
{
students.map((item, index) => {
return (
<li key={item.id}>
<span>名字: {item.name} 年龄: {item.age}</span>
<button onClick={e => incrementAgeWithIndex(index)}>age+1</button>
</li>
)
})
}
</ul>
</div>
)
}
useEffect
class组件实现响应式
import React, { PureComponent } from 'react'
export default class ClassCounterTitleChange extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
// 组件挂载,只会执行一次
componentDidMount() {
// 1.修改DOM的操作
document.title = this.state.counter;
// 2.订阅事件的操作
console.log("订阅一些事件");
// 3.网络请求的操作
// ...
}
// 组件即将卸载
componentWillUnmount() {
console.log("取消事件订阅");
}
// 劣质的响应式:state 发生变化,就在这个生命周期进行修改
componentDidUpdate() {
document.title = this.state.counter;
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.setState({counter: this.state.counter + 1})}>+1</button>
</div>
)
}
}
useEffect实现响应式
import React, { useState, useEffect } from 'react'
export default function HookCounterChangeTitle() {
const [counter, setCounter] = useState(0);
// 第二个参数不传,只要state发生变化,就会执行这个副作用函数
useEffect(() => {
document.title = counter;
})
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e => setCounter(counter + 1)}>+1</button>
</div>
)
}
useEffect 模拟 订阅和取消订阅
export default function EffectHookCancelDemo() {
const [count, setCount] = useState(0);
const cancelEffct = useEffect(() => {
console.log("订阅一些事件");
return () => {
console.log("取消订阅事件")
}
}, []); // 第二个参数为空,只会执行一次,类似class中的componentDidMount
return (
<div>
<h2>EffectHookCancelDemo</h2>
<h2>{count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
</div>
)
}
多useEffect
import React, { useState, useEffect } from 'react'
export default function MultiEffectHookDemo() {
const [count, setCount] = useState(0);
const [isLogin, setIsLogin] = useState(true);
useEffect(() => {
console.log("修改DOM", count);
}, [count]);
useEffect(() => {
console.log("订阅事件");
}, []);
useEffect(() => {
console.log("网络请求");
}, []);
return (
<div>
<h2>MultiEffectHookDemo</h2>
<h2>{count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<h2>{isLogin ? "m2": "未登录"}</h2>
<button onClick={e => setIsLogin(!isLogin)}>登录/注销</button>
</div>
)
}
useContext
App.jsx
import React, { useState, createContext } from 'react';
export const UserContext = createContext();
export const ThemeContext = createContext();
export default function App() {
const [show, setShow] = useState(true);
return (
<div>
<UserContext.Provider value={{name: "m2", age: 18}}>
<ThemeContext.Provider value={{fontSize: "30px", color: "red"}}>
<ContextHookDemo/>
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
component.jsx
import React, { useContext } from 'react';
import { UserContext, ThemeContext } from "../App";
export default function ContextHookDemo(props) {
const user = useContext(UserContext);
const theme = useContext(ThemeContext);
console.log(user, theme);
return (
<div>
<h2>ContextHookDemo</h2>
</div>
)
}
useReducer
useReducer是useState的替代方案,可以简化state复杂变化时的逻辑,但它并非是全局的,而是单个组件的状态管理,所以代替不了redux之类的全局状态管理库。
reducer.js
export default function reducer(state, action) {
switch(action.type) {
case "increment":
return {...state, counter: state.counter + 1};
case "decrement":
return {...state, counter: state.counter - 1};
default:
return state;
}
}
home.jsx
import React, { useState, useReducer } from 'react';
import reducer from './reducer';
export default function Home() {
// const [count, setCount] = useState(0);
const [state, dispatch] = useReducer(reducer, {counter: 0});
return (
<div>
<h2>Home当前计数: {state.counter}</h2>
<button onClick={e => dispatch({type: "increment"})}>+1</button>
<button onClick={e => dispatch({type: "decrement"})}>-1</button>
</div>
)
}
useCallback
useCallback是用来缓存函数了,一般是结合memo来使用的,当useCallback生成的函数传递给一个memo生成的组件时,页面中的状态发生变化,该memo生成的组件不会重新渲染。
useCallback的使用场景: 在将一个组件中的函数, 传递给memo生成的子组件进行回调使用时, 可使用useCallback对函数进行处理,然后传递该函数给子组件。
import React, {useState, useCallback, memo} from 'react';
const MyButton = memo((props) => {
console.log("HYButton重新渲染: " + props.title);
return <button onClick={props.increment}>HYButton +1</button>
});
export default function CallbackHookDemo02() {
console.log("CallbackHookDemo02重新渲染");
const [count, setCount] = useState(0);
const [show, setShow] = useState(true);
const increment1 = () => {
console.log("执行increment1函数");
setCount(count + 1);
}
const increment2 = useCallback(() => {
console.log("执行increment2函数");
setCount(count + 1);
}, [count]);
return (
<div>
<h2>CallbackHookDemo: {count}</h2>
<MyButton title="btn1" increment={increment1}/>
<MyButton title="btn2" increment={increment2}/>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
useCallback
useCallback的使用场景: 在将一个组件中的函数, 传递给memo生成的子组件进行回调使用时, 可使用useCallback对函数进行处理,然后传递该函数给子组件。
函数组件,函数体就是render函数,所以每次state发生变化,就会重新执行render函数,缓存过的函数可以直接返回,当缓存函数作为属性传递给memo生成的组件时,不会导致memo生成的组件重新render。而非memo生成的组件,则会重新调用render函数,因为非缓存的函数,它的引用不一样,毕竟每次都是重新声明创建的。
import React, {useState, useCallback, memo} from 'react';
const MyButton = memo((props) => {
console.log("HYButton重新渲染: " + props.title);
return <button onClick={props.increment}>HYButton +1</button>
});
export default function CallbackHookDemo() {
console.log("CallbackHookDemo02重新渲染");
const [count, setCount] = useState(0);
const [show, setShow] = useState(true);
const increment1 = () => {
console.log("执行increment1函数");
setCount(count + 1);
}
// 缓存传入的函数
const increment2 = useCallback(() => {
console.log("执行increment2函数");
setCount(count + 1);
}, [count]);
return (
<div>
<h2>CallbackHookDemo: {count}</h2>
<MyButton title="btn1" increment={increment1}/>
<MyButton title="btn2" increment={increment2}/>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
useMemo
缓存 值,就像是vue中的computed一样,但监听的属性值发生变化时,会重新计算,重新缓存。
函数组件,函数体就是render函数,所以每次state发生变化,就会重新执行render函数,缓存过的值可以直接返回,不需要重新计算。
复杂计算的缓存
import React, {useState, useMemo} from 'react';
function calcNumber(count) {
console.log("calcNumber 重新计算");
let total = 0;
for (let i = 1; i <= count; i++) {
total += i;
}
return total;
}
export default function MemoHookDemo() {
const [count, setCount] = useState(10);
const [show, setShow] = useState(true);
// const total = calcNumber(count);
const total = useMemo(() => {
return calcNumber(count);
}, [count]);
return (
<div>
<h2>计算数字的和: {total}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
结合memo来进行性能优化
import React, { useState, memo, useMemo } from 'react';
const MyInfo = memo((props) => {
console.log("MyInfo重新渲染");
return <h2>名字: {props.info.name} 年龄: {props.info.age}</h2>
});
export default function MemoHookDemo() {
console.log("MemoHookDemo重新渲染");
const [show, setShow] = useState(true);
// const info = { name: "m2", age: 18 };
const info = useMemo(() => {
return { name: "m2", age: 18 };
}, []);
return (
<div>
<MyInfo info={info} />
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
useRef
原来的ref有三种方式可以拿到dom,但hooks中使用useRef拿dom更加方便了。
ref拿到DOM引用
import React, { useEffect, useRef } from 'react';
class TestCpn extends React.Component {
render() {
return <h2>TestCpn</h2>
}
}
function TestCpn2(props) {
return <h2>TestCpn2</h2>
}
export default function RefHookDemo() {
const titleRef = useRef();
const inputRef = useRef();
const testRef = useRef();
const testRef2 = useRef();
function changeDOM() {
titleRef.current.innerHTML = "Hello World";
inputRef.current.focus();
console.log(testRef.current);
console.log(testRef2.current);
}
return (
<div>
<h2 ref={titleRef}>RefHookDemo</h2>
<input ref={inputRef} type="text"/>
<TestCpn ref={testRef}/>
<TestCpn2 ref={testRef2}/>
<button onClick={e => changeDOM()}>修改DOM</button>
</div>
)
}
使用forwardRef配合ref来转发ref
有点绕,但是之前有这样做过,那时候使用的时react的createRef。
import React, { useRef, forwardRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return <input ref={ref} type="text"/>
})
export default function ForwardRefDemo() {
const inputRef = useRef();
return (
<div>
<MyInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
ref引用其它数据
ref引用其它数据时,它会缓存上一次render时候的值。这样就可以记录之前的值了。
import React, { useRef, useState, useEffect } from 'react'
export default function RefHookDemo() {
const [count, setCount] = useState(0);
const numRef = useRef(count);
useEffect(() => {
numRef.current = count;
}, [count])
return (
<div>
{/* <h2>numRef中的值: {numRef.current}</h2>
<h2>count中的值: {count}</h2> */}
<h2>count上一次的值: {numRef.current}</h2>
<h2>count这一次的值: {count}</h2>
<button onClick={e => setCount(count + 10)}>+10</button>
</div>
)
}
useImperativeHandle
翻译:使用命令句柄
这里类似于代理模式,上面通过使用forwardRef配合ref来转发ref,让子组件的dom暴露在父组件面前,这样不友好,其实父组件只想要调用子组件dom的focus方法,这时我们可以给子组件的dom做一层代理,只暴露安全的方法。
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
const MyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}), [inputRef])
return <input ref={inputRef} type="text"/>
})
export default function UseImperativeHandleHookDemo() {
const inputRef = useRef();
return (
<div>
<MyInput ref={inputRef}/>
{/* 父组件只能使用这一个方法 */}
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
useLayoutEffect
useEffect是在浏览器渲染后执行,useLayoutEffect是在浏览器渲染前执行。
官方不建议你是用它,但是官方还是出了这个hooks。
useLayoutEffect 主要在useEffect函数结果不满意的时候才被用到,一般是当处理dom改变带的副作用,这个hook执行时,浏览器并未对dom进行渲染,相对于useEffect执行要早。
也就说,做动画效果的话,它更可靠一些。那就来比一比 useEffect和useLayoutEffect,给案例做个限制
useEffect
每次点击按钮会两次渲染,而界面中也会出现两次渲染,分别是渲染58个@ 和 渲染 随机数。说白了,就是58个@被渲染到屏幕上之后,就去执行useEffect中的副作用函数,然后又渲染一个随机数到界面中。
import React, { useState, useEffect } from 'react'
export default function EffectCounterDemo() {
const [count, setCount] = useState(10);
useEffect(() => {
if (count === '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@') {
setCount(Math.random() + 200)
}
}, [count]);
return (
<div>
<h2>数字: {count}</h2>
<button onClick={e => setCount('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@')}>修改数字</button>
</div>
)
}
useLayoutEffect
界面中只有一次渲染,渲染1会被渲染随机数给覆盖掉。说白了,就是58个@被渲染到屏幕上之前,就去执行useLayoutEffect中的副作用函数,然后只渲染一个随机数到界面中。
import React, { useState, useEffect, useLayoutEffect } from 'react'
export default function LayoutEffectCounterDemo() {
const [count, setCount] = useState(10);
useLayoutEffect(() => {
if (count === '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@') {
setCount(Math.random() + 200)
}
}, [count]);
return (
<div>
<h2>数字: {count}</h2>
<button onClick={e => setCount('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@')}>修改数字</button>
</div>
)
}
自定义hooks
这里的自定义hooks和vue3中的自定义hooks本质上是一样的,只是按照那种规范命名的方式去定义工具函数,然后尽量的让那种用法看起来像react的hooks而已。
最常见的就是将react的hooks进行一层封装,然后暴露出符合业务要求的hooks。
比如context共享、获取滚动位置,localstorage存储等等。
conetxt 共享
app.jsx
import React, { useState, createContext } from 'react';
export const UserContext = createContext();
export const ThemeContext = createContext();
export default function App() {
const [show, setShow] = useState(true);
return (
<div>
<UserContext.Provider value={{name: "m2", age: 18}}>
<ThemeContext.Provider value={{fontSize: "30px", color: "red"}}>
<ContextHookDemo/>
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
useUserContext.js
import { useContext } from "react";
import { UserContext, TokenContext } from "../App";
function useUserContext() {
const user = useContext(UserContext);
const token = useContext(TokenContext);
return [user, token];
}
export default useUserContext;
demo.jsx
import React, { useContext } from 'react';
import useUserContext from './hooks/useUserContext';
export default function CustomContextShareHook() {
const [user, token] = useUserContext();
console.log(user, token);
return (
<div>
<h2>CustomContextShareHook</h2>
</div>
)
}
获取滚动位置
useScrollPosition.js
import { useState, useEffect } from 'react';
function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
const handleScroll = () => {
setScrollPosition(window.scrollY);
}
document.addEventListener("scroll", handleScroll);
return () => {
document.removeEventListener("scroll", handleScroll)
}
}, []);
return scrollPosition;
}
export default useScrollPosition;
demo.jsx
import React, { useEffect, useState } from 'react'
import useScrollPosition from './hooks/useScrollPosition'
export default function CustomScrollPositionHook() {
const position = useScrollPosition();
return (
<div style={{padding: "1000px 0"}}>
<h2 style={{position: "fixed", left: 0, top: 0}}>CustomScrollPositionHook: {position}</h2>
</div>
)
}
localstorage存储
useLocalStorage.js
import {useState, useEffect} from 'react';
function useLocalStorage(key) {
const [value, setValue] = useState(() => {
const value = JSON.parse(window.localStorage.getItem(key));
return value;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [value]);
return [value, setValue];
}
export default useLocalStorage;
demo.jsx
import React, { useState, useEffect } from 'react';
import useLocalStorage from './hooks/useLocalStorage';
export default function CustomDataStoreHook() {
const [student, setStudent] = useLocalStorage("student");
return (
<div>
<h2>Student: {student}</h2>
<button onClick={e => setStudent("aiyoudiao")}>设置Student</button>
</div>
)
}
总结
内容偏长,但是比较常见,也算是React实践必备吧。antd、transition、redux、react-redux、middleware、reducer、router、hooks。后面有空的话,再出一篇next的。