我学会了,react上手知识点(下篇)

简介: 本篇文章记录的是上手react并做好一个React项目的大概知识点,比如antd、transition、redux、react-redux、middleware、reducer、router、hooks等等。

前言

本篇文章记录的是上手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的。

目录
相关文章
|
3月前
|
Web App开发 存储 前端开发
React 之 Scheduler 源码中的三个小知识点,看看你知不知道?
本篇补充讲解 Scheduler 源码中的三个小知识点。
90 0
|
10月前
|
前端开发 JavaScript 测试技术
React知识点系列(5)-每天10个小知识
React知识点系列(5)-每天10个小知识
39 0
|
10月前
|
缓存 前端开发 JavaScript
React知识点系列(4)-每天10个小知识
React知识点系列(4)-每天10个小知识
20 0
|
10月前
|
前端开发 JavaScript 中间件
React知识点系列(3)-每天10个小知识
React知识点系列(3)-每天10个小知识
28 0
|
10月前
|
存储 缓存 前端开发
React知识点系列(6)-每天10个小知识
React知识点系列(6)-每天10个小知识
38 0
|
3月前
|
XML 资源调度 前端开发
React基础知识点
React基础知识点
63 0
|
3月前
|
缓存 监控 前端开发
这个知识点,是React的命脉
这个知识点,是React的命脉
|
3月前
|
XML 存储 前端开发
react部分知识点总结
react部分知识点总结
47 0
|
10月前
|
缓存 前端开发 JavaScript
React知识点系列(8)-每天10个小知识
React知识点系列(8)-每天10个小知识
34 0
|
10月前
|
缓存 前端开发 JavaScript
React知识点系列(7)-每天10个小知识
React知识点系列(7)-每天10个小知识
42 0