الرد باستخدام خطافات React: ربط وظيفي

مرحبا! اسمي إيغور شامايف ، أنا كبير مهندسي التطوير في فريق SmartData. أنا منخرط في تطوير مكدس لنظام BI التحليلي الداخلي. في شركتنا ، يتم قبول React كمعيار رئيسي لبناء واجهات المستخدم. مثل معظم مجتمع React ، نستخدم الخطافات على نطاق واسع في عملنا اليومي.



التعلم المستمر هو جزء لا يتجزأ من وظيفة أي مطور جيد. لذلك ، أود اليوم أن أقدم مساهمتي المتواضعة في هذه العملية وأن أقدم دليلًا صغيرًا لأولئك الذين بدأوا في تعلم React بنشاط والعمل مع الخطافات. وعلى طول الطريق ، يمنحك أداة صغيرة ومفيدة للعمل مع معيار React الجديد.



في ترجمة المقالة Debouncing باستخدام React Hooks ، تعلمنا كيف ، بدون مكتبات الطرف الثالث ، وباستخدام قدرات React فقط ، يمكنك إنشاء خطاف في عدة أسطر من التعليمات البرمجية للعمل مع التغييرات البطيئة في القيم المتغيرة. الآن أقترح التفكير في خطاف مفيد آخر سيساعدنا في تأجيل مكالمة الوظيفة. إذا تم استدعاء الوظيفة عدة مرات متتالية ، فلن تحدث المكالمة الفعلية إلا بعد فترة التأخير التي حددناها. هذا هو ، فقط للمكالمة الأخيرة في السلسلة. الحل أيضًا مضغوط جدًا وسهل التنفيذ في React. إذا كنت مهتمًا ، من فضلك ، تحت القط.





React . . , React , .



...
import { useRef, useEffect } from "react";

export default function useDebouncedFunction(func, delay, cleanUp = false) {
  const timeoutRef = useRef();

  function clearTimer() {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = undefined;
    }
  }

  useEffect(() => (cleanUp ? clearTimer : undefined), [cleanUp]);

  return (...args) => {
    clearTimer();
    timeoutRef.current = setTimeout(() => func(...args), delay);
  };
}


, - , . , . Material-UI.



import React from "react";
import { makeStyles, Typography, Slider } from "@material-ui/core";
import useDebouncedFunction from "./useDebouncedFunction";
import apiRequest from "./apiRequest";

const useStyles = makeStyles({
  root: {
    width: 300
  }
});

function valuetext(value) {
  return `${value}°C`;
}

export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}




, - , . , . , - , . , , console.log():



export default function valueLogging(value) {
  console.log(`Request processed. Value: ${value}`);
}


handleChange() valueLogging() , :



  const handleChange = (event, newValue) => {
    setValue(newValue);
    valueLogging(newValue);
  };




… , . valueLogging() . . . ?



1. useDebounce value .



useDebounce debouncedValue, . useEffect valueLogging(). - :



export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);
  const [changedByUser, setChangedByUser] = React.useState(false);

  const debouncedValue = useDebounce(value, 300);

  useEffect(() => {
    if (changedByUser) {
      valueLogging(debouncedValue);
    }
  }, [debouncedValue]);

  const handleChange = (event, newValue) => {
    setValue(newValue);
    if (!changedByUser) {
        setChangedByUser(true);
    }
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}


, , ? , useEffect, , valueLogging() . , . , useEffect , valueLogging(). , React useEffect changedByUser. , .



?



2. valueLogging() handleChange().



, :



export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const debouncedValueLogging = useDebouncedFunction(valueLogging, 300);

  const handleChange = (event, newValue) => {
    setValue(newValue);
    debouncedValueLogging(newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}


useDebouncedFunction, .



React:



import { useRef } from "react";

export default function useDebouncedFunction(func, delay) {
  const ref = useRef(null);

  return (...args) => {
    clearTimeout(ref.current);
    ref.current = setTimeout(() => func(...args), delay);
  };
}


, . , . -, useRef ( useRef). : , React. -, . current .



, useRef, , DOM-. , . .

setTimeout() . timeoutId, setTimeout(), ref.current. , useDebouncedFunction. timeoutId clearTimeout(). , . , valueLogging() 300 . .



, ... useRef? ?

let timeoutId; :



export default function useDebouncedFunction(func, delay) {
  let timeoutId;

  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}


React. React . , , :





- , debouncedValueLogging() timeoutId. .



, :





! . , .



, . , - . . , , ?



, - . , , .



.



-, , value . , , RangeSlider .



import React, { useState } from "react";
import { ThemeProvider, createMuiTheme, Typography } from "@material-ui/core";
import RangeSlider from "./RangeSlider";

const theme = createMuiTheme({});

export default function App() {
  const [sliderShown, setSliderShown] = useState(true);

  //      
  function handleValueChange(value) {
    if (value[1] === 100) {
      setSliderShown(false);
    }
  }

  return (
    <ThemeProvider theme={theme}>
      {sliderShown ? (
        <RangeSlider onValueChange={handleValueChange} />
      ) : (
        <Typography variant="h2">Too hot!</Typography>
      )}
    </ThemeProvider>
  );
}


-, RangeSlider , , , . , - , , , . .



import React from "react";
import { makeStyles, Typography, Slider } from "@material-ui/core";
import useDebouncedFunction from "./useDebouncedFunction";
import valueLogging from "./valueLogging";
import checkIfOptimal from "./checkIfOptimal";

const useStyles = makeStyles({
  root: {
    width: 300
  }
});

function valuetext(value) {
  return `${value}°C`;
}

export default function RangeSlider(props) {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);
  const [isOptimal, setIsOptimal] = React.useState(true);

  //  
  const debouncedValueLogging = useDebouncedFunction(
    newValue => valueLogging(newValue),
    300
  );

  //   
  const debouncedValueCheck = useDebouncedFunction(
    newValue => checkIfOptimal(newValue, setIsOptimal),
    300
  );

  const handleChange = async (event, newValue) => {
    setValue(newValue);
    debouncedValueLogging(newValue);
    debouncedValueCheck(newValue);
    if (props.onValueChange) {
      props.onValueChange(newValue);
    }
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
        style={{ color: isOptimal ? "#4caf50" : "#f44336" }}
      />
    </div>
  );
}


-, checkIfOptimal():



//   
export default function checkIfOptimal(newValue, setIsOptimal) {
  return setIsOptimal(10 < newValue[0] && newValue[1] < 80);
}


, :





, , checkIfOptimal().





, React :



Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

in RangeSlider (at App.js:20)

? , true/false . setIsOptimal(). , 300 . . React. . ?



useDebouncedFunction: cleanUp. true, .



import { useRef, useEffect } from "react";

export default function useDebouncedFunction(func, delay, cleanUp = false) {
  const timeoutRef = useRef();

  //  
  function clearTimer() {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = undefined;
    }
  }

  //     ,  cleanUp   true
  //       
  useEffect(() => (cleanUp ? clearTimer : undefined), [cleanUp]);

  return (...args) => {
    clearTimer();
    timeoutRef.current = setTimeout(() => func(...args), delay);
  };
}


useEffect , . clearTimer() .

.



  //   
  const debouncedValueCheck = useDebouncedFunction(
    newValue => checkIfOptimal(newValue, setIsOptimal),
    300,
    true
  );


.





, . , .



?



, . checkIfOptimal() — , . checkIfOptimal() , , , .



? useDebouncedFunction , , , .



, .



. , .



, , codesandbox. :



useDebouncedFunction codesandbox




All Articles