在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。使用 React Hooks 相比于从前的类组件有以下几点好处:

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state以及其他的 React 特性
Hooks只能在函数式组件中使用,既无状态组件(所有钩子在用时都要先引入)

1、Hook 使用规则

Hook 就是JavaScript 函数,但是使用它们会有两个额外的规则:
1、只能在函数最外层调用 Hook。不要在循环、条件判断或者嵌套函数(子函数)中调用。
2、只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
3、在多个useState()调用中,渲染之间的调用顺序必须相同

2、useDispatch

使用这个 hook 能得到 redux store 的 dispatch 方法引用,通常用于“手动” dispatch action

  const dispatch = useDispatch()

例如:

import React from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()

  return (
    <div>
      <span>{value}</span>
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}

之前在使用 connect 的时候,我们通常使用mapDispatchToPropsactionCreator封装一下dispatch action的过程,然而使用 useDispatch()的时候却需要“手动”调用 dispatch()方法。

然而,实际上 Redux Hooks 曾经提供一个叫 useActions()的 API 起到类似于 mapDispatchToProps 和 bindActionCreators 的作用,但后来被 Dan Abramov (React和Redux的核心成员)毙掉了,主要有两个原因:

  • 避免将 actionCreator 的 dependency 也不得不加进 useActions() 中,导致代码冗长(dependency array)
  • 在 Hooks 的世界中,直接使用 dispatch() 和 actionCreator 让代码更直接。相反,使用类似mapDispatchToProps 和 bindActionCreators 虽然看似缩短了代码量,却让开发者一定程度上丢失了对 redux 整体数据流动的视野和理解
3、useSelector

组件可以通过useSelector访问store中释放的state数据

import React from "react";
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";

const initialState = { 
    num: 0 ,
    val : 100,
    list : [{
        key: 1,
        name: '张三'
    },{
        key: 2,
        name: '李四'
    },{
        key: 3,
        name: '王五'
    },]
};

const reducer = (state, action) => {
    switch (action.type) {
        case "decrement":
            return { ...state, num: state.num - 1 };
        case "increment":
            return { ...state, num: state.num + 1 };
        default:
        
        return state;
    }
};

const store = createStore(reducer, initialState);

const App = () => {
    return (
        <div>
            <h2>---父组件---</h2>
            <Provider store={store}>
                <Child1 />
            </Provider>
        </div>
    )
}

const Child1 = () => {
    const num = useSelector(state => {
        console.log('--++++--',state)
        return state.num
    });
    const dispatch = useDispatch();
    return (
        <div>
            <h3>---子组件---</h3>
            <button onClick={() => dispatch({ type: "increment" })}>+</button>
            Number: {num}
            <button onClick={() => dispatch({ type: "decrement" })}>-</button>
        </div>
    );
};

export default App;

在这里插入图片描述
其实,selector 这个概念跟 hooks 没有关系,并且很早就被提出了。

  • selector 是函数
  • selector 的作用是根据 redux 的 state 查找、筛选、处理后获得一个或者多个派生的数据

useSelector() 这个 hook,它的参数就是 selector 并返回 selector 的计算结果
最重要的是,这个 hook 会订阅 redux store(牢记这点),所以每次 redux state有更新,useSelector() 里的 selector 就会重新计算一次返回新的结果,并重新渲染当前组件。(如下面展示的)

import React from "react";
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";

const initialState = 同上

const reducer = 同上

const store = createStore(reducer, initialState);

const App = () => {
    return (
        console.log('---父组件---'),
        <div>
            <h2>---父组件---</h2>
            <Provider store={store}>
                <Child1 />
            </Provider>
        </div>
    )
}

const Child1 = () => {
    const num = useSelector(state => {
        return state.num
    });
    const dispatch = useDispatch();
    return (
        console.log('---子组件---'),
        <div>
            <h3>---子组件---</h3>
            <button onClick={() => dispatch({ type: "increment" })}>+</button>
            Number: {num}
            <button onClick={() => dispatch({ type: "decrement" })}>-</button>
        </div>
    );
};

export default App;

在这里插入图片描述

4、useStore

关于这个api,我也是没怎么用过,不过上网查了一波,就如字面上的意思,useStore() 这个 Hook 直接获取到了 Redux store 的引用,所以可以使用到更“底层”的API 如:

  • getState()
  • dispatch(action)
  • subscribe(listener)
  • replaceReducer(nextReducer)

其中,useStore().dispatch 其实等同于 useDispatch()

const dispatch = useDispatch();
const store = useStore();
console.log("they are equal: " + (dispatch === store.dispatch)); // true

store 中的 getState()方法,调用它可以得到当前 redux state,但它并不等同于 useSelector()

最大的区别在于: getState()只会获得当前时刻的 redux state,之后state 更新不会导致这个方法被再次调用,也不会导致重新渲染了。

因此,根据业务需求:

  1. 假如当前组件需要监听 redux state 的变化,并根据 redux state的更新而渲染不同的视图或者有不同行为—— 那么就应该使用 useSelector Hook
  2. 假如当前组件只是为了在 redux state中一次性查询某个数据/状态,并不关心(或刻意忽略)之后的更新—— 那么就应该使用 useStore().getState()
5、两者替代connect

平时我们使用redux的时候可能使用HOC的形式,mapStateToPropsmapDispatchToProps加强组件,例如:

import React from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/actions';

const mapStateToProps = store => ({
  count: store.count
});

const mapDispatchToProps = dispatch => ({
  increment: count => dispatch(actions.increment(count)),
  decrement: count => dispatch(actions.decrement(count))
});

@connect(mapStateToProps,mapDispatchToProps)
class App extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const {count, increment, decrement} = this.props;

    return (
      <div>
        <h1>The count is {count}</h1>
        <button onClick={() => increment(count)}>+</button>
        <button onClick={() => decrement(count)}>-</button>
      </div>
    );
  }
}
export default App;
或不用@connect的形式,用下面这种连接
export default connect(mapStateToProps, mapDispatchToProps)(App);

我们也可以使用useDispatchuseSelector来实现类似的需求

import React from 'react';
import { createSelector } from 'reselect';
import * as actions from '../actions/actions';
import { useSelector, useDispatch } from 'react-redux';

const App = () => {
  const dispatch = useDispatch();
  const count = useSelector(createSelector(store => store.count, state => state));

  return (
    <div>
      <h1>The count is {count}</h1>
      <button onClick={() => dispatch(actions.increment(count))}>+</button>
      <button onClick={() => dispatch(actions.decrement(count))}>-</button>
    </div>
  );
}

export default App;

有的没有在全局引入所有action,可以单独引入不同action并派发

 import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { updateTel, updateName } from './action'

export default () => {
  const formData = useSelector(state => {
    	return state
  })
  const dispatch = useDispatch()
  console.log({formData});

  return <div>
      form: <br/>
      姓名: <input type="text" onChange={(e) => {
        dispatch(updateName(e.target.value))
      }}/>
      电话: <input type="tel" onChange={(e) => {
        dispatch(updateTel(e.target.value))
      }}/>
  </div>

}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐