איך לצמצם Boilerplate ב Redux - חלק 3

06/08/2020

בשני החלקים הקודמים למדנו איך לצמצם Boilerplate ביישום Redux באמצעות טיפול ב Action Creators וב Reducer. היום אני רוצה לדבר על הקומפוננטות ולהראות איך לכתוב הרבה פחות קוד בתוך קומפוננטה שמחוברת ל Redux ועדיין לקבל בדיוק את אותה תוצאה.

1. איפה כאן ה Boilerplate?

נתבונן בקומפוננטה הבאה מתוך דוגמת ה Todo המפורסמת מהאתר של רידאקס. הקומפוננטה מסודרת בשני קבצים אבל אני מאחד אותם בשביל שיהיה קל לראות:

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'

import React from 'react'
import PropTypes from 'prop-types'

const Link = ({ active, children, onClick }) => (
    <button
       onClick={onClick}
       disabled={active}
       style={{
           marginLeft: '4px',
       }}
    >
      {children}
    </button>
)

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

איפה כאן ה Boilerplate? נו, זה ה Higher Order Component כמובן: הגדרת הפונקציה mapStateToProps שתקועה בכל קומפוננטה, הגדרת הפונקציה mapDispatchToProps, וכמובן הקריאה ל connect.

2. איך לצמצם את הקוד המיותר

המעבר מ Higher Order Components ל Custom Hooks איפשר לכותבי הספריה react-redux להציע API נקי יותר. אפשר לקרוא את הפרטים בתיעוד של react-redux כאן: https://react-redux.js.org/api/hooks

בקצרה אנחנו נשתמש ב useSelector במקום ב mapStateToProps, וב useDispatch במקום ב mapDispatchToProps. הנה הקוד אחרי התיקון:

import React from "react";
import PropTypes from "prop-types";
import { useSelector, useDispatch } from "react-redux";
import { setVisibilityFilter } from "../actions";

const Link = props => {
  const { children, filter } = props;
  const active = useSelector(state => state.visibilityFilter === filter);
  const dispatch = useDispatch();
  const onClick = () => dispatch(setVisibilityFilter(filter));

  return (
    <button
      onClick={onClick}
      disabled={active}
      style={{
        marginLeft: "4px"
      }}
    >
      {children}
    </button>
  );
};

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
};

export default Link;

הקוד שלנו כולל עכשיו רק קומפוננטה אחת וכל המידע שלה מחושב במקום אחד בתחילת הפונקציה. אם בעתיד נצטרך קומפוננטות מורכבות יותר יהיה לנו הרבה יותר קל לשבור את הקוד למספר קומפוננטות שכל אחת עוסקת בענייניה. הפרידה מ Higher Order Component יצרה קוד נקי יותר וגם תוצאה רזה יותר מבחינת ה Virtual DOM.