hooks 高阶函数Redux注入

53 阅读3分钟

react-china.org/t/topic/340… React-Redux 官方 Hooks 文档说明

使用useSelector useDispatch 替代connect cloud.tencent.com/developer/a…

使用 React Hooks 代替 Redux zhuanlan.zhihu.com/p/66020264

Hooks注入mapStateToProps/mapPopularDispatchToProps属性

import { connect } from 'react-redux';

const mapStateToProps = state => ({
  myProfile: state.apps.myProfile,
});
const mapPopularDispatchToProps = dispatch => ({
  onOpenChat: ({
                 no
               }) =>
    dispatch(actions.onOpenChat({
      no
    })),
});
export default connect(
  mapStateToProps,
  mapPopularDispatchToProps,
)(activiteListCom);

//获取
const activiteListCom = ((props) => {
  const {  myProfile, onOpenChat } = props;
  
  //-----------------------------------------------
  //新的api里直接这样就可以拿的到
  import {useSelector} from 'react-redux';

//组件里
  ...
const state = useSelector(state => {
    return {
      chatBadgeCount: state.chat.chatBadgeCount,
      letterBadgeCount: state.sideMenu.letterBadgeCount,
    };
  });
  //使用
    const {myProfile, personal} = state; 
  export default CustomerService;

发送事件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>
  )
}

useStore()

const store = useStore()

这个 hook 返回传递给 组件的 Redux sotore 的引用。

这个 hook 也许不应该被经常使用。 你应该将 useSelector() 作为你的首选。但是,在一些不常见的场景下,你需要访问 store,这个还是有用的,比如替换 store 的 reducers。

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

export const CounterComponent = ({ value }) => {
  const store = useStore()

  // EXAMPLE ONLY! Do not do this in a real app.
  // The component will not automatically update if the store state changes
  return <div>{store.getState()}</div>
}

自定义 context

 组件允许你通过 context 参数指定一个可选的 context。在你构建复杂的可复用的组件时,你不想让你自己的私人 store 与使用这个组件的用户的 Redux store 发生冲突,这个功能是很有用的,

通过使用 hook creator 函数来创建自定义 hook,从而访问可选的 context。

    import React from 'react'
import {
  Provider,
  createStoreHook,
  createDispatchHook,
  createSelectorHook
} from 'react-redux'

const MyContext = React.createContext(null)

// Export your custom hooks if you wish to use them in other files.
export const useStore = createStoreHook(MyContext)
export const useDispatch = createDispatchHook(MyContext)
export const useSelector = createSelectorHook(MyContext)

const myStore = createStore(rootReducer)

export function MyProvider({ children }) {
  return (
    <Provider context={MyContext} store={myStore}>
      {children}
    </Provider>
  )
}
    

性能

正如上文提到的,在一个 action 被分发(dispatch) 后,useSelector() 默认对 select 函数的返回值进行引用比较 ===,并且仅在返回值改变时触发重渲染。但是,不同于 connect(),useSelector()并不会阻止父组件重渲染导致的子组件重渲染的行为,即使组件的 props 没有发生改变。

如果你想要类似的更进一步的优化,你也许需要考虑将你的函数组件包裹在 React.memo() 中:

    const CounterComponent = ({ name }) => {
  const counter = useSelector(state => state.counter)
  return (
    <div>
      {name}: {counter}
    </div>
  )
}

export const MemoizedCounterComponent = React.memo(CounterComponent)
    ```
    ```
    /**
 * Created by Webstorm
 * User: King
 * Date: 2020-02-11
 * Time: 10:03
 */
import React, { useRef, useState } from 'react';
import { BlockCom, TextCom, CaveatAlertCom } from '@base';
import { Image, TouchableOpacity } from 'react-native';
import { PullListCom } from '@business';
import { IMG_URL } from '@apis/config';
import { getImageUtils, NavigationUtil } from '@utils';
import { StatusBadgeCom } from '@business';
import moment from 'moment';
import actions from '@actions';
import { connect } from 'react-redux';
import { ToastUtil } from '@service/Toast';
import I18n from '@i18n';


const activiteListCom = ((props) => {
  const { setListData, canDelet, type, deletePrompt, onPostWatch, chatContentData, myProfile, onOpenChat, emptyTip } = props;
  const [getDeletFlag] = useState(canDelet); // 是否存在删除功能
  const caveatAlertRef = useRef();
  const pullRef = useRef();
  /**
   * 跳转页面
   * @method goPage
   * @param type {String} 区分聊天还是信件类型
   * @param femaleId {String} 女士ID
   * @param online {Boolean} 女士是否在线
   * @param engName {String} 女士名字
   * @param iconPath {String} 女士头像
   * @param no {String} 女士编号
   * @return {Any}
   */
  const goPage = async ({ type, femaleId, online, engName, iconPath, no }) => {
    switch (type) {
      case 'chat':
        await onOpenChat({
          maleId: myProfile.id,
          femaleId,
          chatContentData,
          engName,
          iconPath,
          online,
          no
        });
        break;
      case 'email':
        NavigationUtil.navigate('WirteLetter', {
          type: 'reply',
          icon: { senderIcon: iconPath },
          name: engName,
          id: femaleId,
        });
        break;
    }
  };

  /**
   * 處理按鈕
   * @method _renderButton
   * @param type {String} 区分聊天还是信件类型
   * @param item {Object} 女士信息
   * @return {Any}
   */
  const _renderButton = ({ type, item }) => {
    const { femaleId, femaleName, femaleIconPath, onlineState, no } = item;
    // 聊天参数
    const _chatParams = {
      femaleId,
      engName: femaleName,
      iconPath: femaleIconPath,
      online: onlineState,
      no
    };
    if (!type) {
      return null;
    }
    const _keys = {
      'chat': {
        imgW: 18,
        imgH: 18,
        imgPath: getImageUtils.poke_me_chat,
      },
      'email': {
        imgW: 18,
        imgH: 14,
        imgPath: getImageUtils.poke_me_mail,
      },
    };
    return <TouchableOpacity
      onPress={() => goPage({
        type,
        ..._chatParams,
      })}>
      <BlockCom
        flex={false}
        width={77}
        height={30}
        style={{
          backgroundColor: '#ffebe4',
          marginRight: $getScreen.px2dp(10),
        }}
        radius={8}
        center
        middle
      >
        <Image source={_keys[type].imgPath}
               style={{
                 width: $getScreen.px2dp(_keys[type].imgW),
                 height: $getScreen.px2dp(_keys[type].imgH),
               }}/>
      </BlockCom>
    </TouchableOpacity>;
  };

  /**
   * 删除我喜欢的女士
   * @method deletList
   * @param  {String} femaleId 当前女士id
   * @param  {String} id 当前列表id
   * @param  {String} no 女生編號
   * @param  {Number} index 当前女士列表索引
   * @return {any}
   */
  const deletList = async ({ femaleId, id, no, index }) => {
    caveatAlertRef.current.show({
      title: deletePrompt,
      onConfirm: () => {
        const { onDeletList } = props;
        $showLoading(true);
        onDeletList && onDeletList({
          femaleId,
          type,
          id,
          no,
        }, () => {
          $showLoading(false);
          pullRef.current.deleteItem(index);
          ToastUtil.show(I18n.t('common.success'));
        });
      },
    });
  };

  /**
   * 渲染女士列表
   * @method renderItem
   * @param  {Object} item 对象信息
   * @param  {Number} index 索引
   * @return {any}
   */
  const renderItem = ({ item, index }) => {
    const {
      id,
      no,
      femaleName,
      femaleId,
      femaleAge,
      femaleIconPath,
      onlineState,
      date,
      country,
      city,
    } = item;
    return <TouchableOpacity onPress={() => NavigationUtil.navigate('LadyDetails', { no: no })}>
      <BlockCom
        row
        height={103}
        flex={false}
        bgColor={'white'}
        padding={[12, 15]}>
        {/*头像描绘*/}
        <BlockCom
          flex={false}
          marginRight={11}>
          <Image
            source={femaleIconPath ? { uri: IMG_URL + femaleIconPath } : getImageUtils.femaleHead}
            style={$styles.radius(79)}/>
          <BlockCom style={{
            position: 'absolute',
            right: 0,
            bottom: 0,
          }}>
            <StatusBadgeCom
              isOnline={onlineState}
            />
          </BlockCom>
        </BlockCom>
        {/*信息层描绘*/}
        <BlockCom between style={$styles.borB} paddingBottom={13} marginBottom={-12}>
          <BlockCom row flex={false}>
            <BlockCom row center>
              <BlockCom flex={false}
                        maxWidth={'50%'}
              ><TextCom ellipsis>{femaleName}</TextCom>
              </BlockCom>
              <BlockCom flex={false}
                        maxWidth={'50%'}
              ><TextCom ellipsis size={'m'}
                        color={'lightGrey'}>,{femaleAge}</TextCom>
              </BlockCom>
            </BlockCom>
            <BlockCom bottom row>
              <TextCom size={'m'} color={'lightGrey'}
                       style={{ marginRight: $getScreen.px2dp(10) }}>{moment(date)
                .format('YYYY-MM-DD')}</TextCom>
              {getDeletFlag ?
                <TouchableOpacity
                  hitSlop={{
                    left: 100,
                    right: 100,
                    top: 100,
                    bottom: 100
                  }}
                  onPress={() => deletList({
                    femaleId,
                    id,
                    no,
                    index,
                  })}>
                  <Image source={getImageUtils.like_photo_delet}
                         style={{
                           ...$styles.wh(15),
                         }}/>
                </TouchableOpacity> : null}

            </BlockCom>
          </BlockCom>
          <BlockCom row center flex={false}>
            <Image source={getImageUtils.poke_me_positioning}
                   style={{
                     width: $getScreen.px2dp(8),
                     height: $getScreen.px2dp(10),
                     marginRight: $getScreen.px2dp(2),
                   }}/>
            <TextCom>{city},{country}</TextCom>
          </BlockCom>
          <BlockCom flex={false} row>
            {_renderButton({
              type: 'chat',
              item,
            })}
            {_renderButton({
              type: 'email',
              item,
            })}
          </BlockCom>
        </BlockCom>
      </BlockCom>
    </TouchableOpacity>;
  };

//刷新后,通知父页面更新未读图标
  const _onFetch = async (res, params) => {
    console.log('通知父页面');
    const result = await setListData(params);
    onPostWatch && onPostWatch(); // 通知父页面请求完成
    res(result[0]);
  };

  const ListEmptyComponent = () => {
    return <BlockCom flex={false} height={60}><TextCom>Loding</TextCom></BlockCom>;
  };
  /**
   * 渲染整体列表
   * @method _renderList
   * @return {any}
   */
  const _renderList = (() => {
    return <PullListCom
      ref={pullRef}
      renderItem={renderItem}
      onFetch={_onFetch}
      emptyTip={emptyTip}
      emptyHeight={158}
    />;
  })();

  return (
    <BlockCom>
      <CaveatAlertCom ref={caveatAlertRef}/>
      {_renderList}
    </BlockCom>
  );
});

const mapStateToProps = state => ({
  myProfile: state.apps.myProfile,
  chatContentData: state.chat.chatContentData,
});
const mapPopularDispatchToProps = dispatch => ({
  onOpenChat: ({
                 no,
                 maleId,
                 femaleId,
                 chatContentData,
                 engName,
                 iconPath,
                 online
               }) =>
    dispatch(actions.onOpenChat({
      no,
      maleId,
      femaleId,
      chatContentData,
      engName,
      iconPath,
      online
    })),
});
export default connect(
  mapStateToProps,
  mapPopularDispatchToProps,
)(activiteListCom);

    ```