ReactJS: ورقة الغش السنانير





يوم جيد ، أيها الأصدقاء!



إليك دليل لخطافات React الرئيسية: useState ، useEffect ، useLayoutEffect ، useContext ، useReducer ، useCallback ، useMemo ، و UseRef.



الإلهام: ورقة الغش في React Hooks: فتح حلول للمشاكل الشائعة .



الغرض من هذا الدليل هو تقديم لمحة موجزة عن الغرض من كل خطاف وقدراته. بعد وصف الخطاف ، يوجد مثال على رمز لاستخدامه وصندوق رمل لتجاربك.



المجموعة الكاملة من الخطافات متاحة في هذا المستودع .



  1. تنزيل المستودع
  2. تثبيت التبعيات: npm i
  3. تشغيل: بدء npm


توجد الخطافات في دليل "الخطافات". الملف الرئيسي هو index.js. لتنفيذ خطاف معين ، تحتاج إلى إلغاء التعليق على سطور الاستيراد والعرض المقابلة.



بدون مقدمة أخرى.



useState



يسمح لك useState بالعمل مع حالة المتغيرات داخل مكون وظيفي.



حالة متغيرة


لتحديد حالة المتغير ، استدعِ useState بالحالة الأولية كوسيطة: useState (initialValue).



const DeclareState = () => {
  const [count] = useState(1);
  return <div>  - {count}.</div>;
};


تحديث حالة المتغير


لتحديث حالة المتغير ، قم باستدعاء دالة التحديث التي أرجعها useState: const [state، updater] = useState (initialValue).



الرمز:



const UpdateState = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  return (
    <>
      <p> {age} .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


Sandbox:





حالات متعددة متغيرة


في مكون وظيفي واحد ، يمكنك تعريف وتحديث حالات عدة متغيرات.



الرمز:



const MultStates = () => {
  const [age, setAge] = useState(19);
  const [num, setNum] = useState(1);

  const handleAge = () => setAge(age + 1);
  const handleNum = () => setNum(num + 1);

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={handleAge}> !</button>
      <button onClick={handleNum}>   !</button>
    </>
  );
};


Sandbox:





استخدام كائن لتحديد حالة المتغير


بالإضافة إلى السلاسل والأرقام ، يمكن استخدام الكائنات كقيمة أولية. لاحظ أن useStateUpdater يحتاج إلى تمرير الكائن بأكمله لأنه يتم استبداله بدلاً من دمجه مع الكائن السابق.



// setState ( ) - useState ( )
// ,    - {name: "Igor"}

setState({ age: 30 });
//   
// {name: "Igor", age: 30} -  

useStateUpdater({ age: 30 });
//   
// {age: 30} -   


الرمز:



const StateObject = () => {
  const [state, setState] = useState({ age: 19, num: 1 });
  const handleClick = (val) =>
    setState({
      ...state,
      [val]: state[val] + 1,
    });
  const { age, num } = state;

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={() => handleClick('age')}> !</button>
      <button onClick={() => handleClick('num')}>   !</button>
    </>
  );


Sandbox:





تهيئة حالة المتغير باستخدام دالة


يمكن تحديد القيمة الأولية لحالة المتغير بواسطة دالة.



const StateFun = () => {
  const [token] = useState(() => {
    const token = localStorage.getItem("token");
    return token || "default-token";
  });

  return <div> - {token}</div>;
};


وظيفة بدلاً من setState


يمكن أن تكون وظيفة التحديث التي تُرجعها useState أكثر من مجرد وظيفة setState.



const [value, updateValue] = useState(0);
//    ,  ,  
updateValue(1);
updateValue((prevVal) => prevVal + 1);


الطريقة الثانية مناسبة للحالات التي يعتمد فيها التحديث على الحالة السابقة.



الرمز:



const CounterState = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <p>   {count}.</p>
      <button onClick={() => setCount(0)}></button>
      <button onClick={() => setCount((prevVal) => prevVal + 1)}>
         (+)
      </button>
      <button onClick={() => setCount((prevVal) => prevVal - 1)}>
         (-)
      </button>
    </>
  );
};


Sandbox:





useEffect



يقبل useEffect وظيفة مسؤولة عن آثار (جانبية) إضافية.



الاستخدام الأساسي


الرمز:



const BasicEffect = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  useEffect(() => {
    document.title = ` ${age} !`;
  });

  return (
    <>
      <p>      .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


Sandbox:





إزالة (إلغاء) أحد التأثيرات


من الممارسات الشائعة إزالة التأثير بعد فترة. يمكن القيام بذلك باستخدام الوظيفة التي يتم إرجاعها بواسطة التأثير الذي تم تمريره إلى useEffect. يوجد أدناه مثال مع addEventListener.



الرمز:



const CleanupEffect = () => {
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  return (
    <>
      <p>        .</p>
    </>
  );
};


Sandbox:





تأثيرات متعددة


يمكن استخدام تأثيرات متعددة في مكون وظيفي.



الرمز:



const MultEffects = () => {
  //   
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  //   
  useEffect(() => {
    console.log(" .");
  });

  return (
    <>
      <p>  .</p>
    </>
  );
};


Sandbox:







لاحظ أنه يمكن تخطي استدعاء useEffect on re-rendering بتمريره مصفوفة فارغة كمتغير ثانٍ.



تبعيات التأثير


الرمز:



const EffectDependency = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1)

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1)
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


Sandbox:







في هذه الحالة ، نقوم بتمرير تبعية randomInt إلى useEffect باعتبارها الوسيطة الثانية ، لذلك تُستدعى الوظيفة في عملية التصيير الأولية ، وأيضًا عندما يتغير randomInt.



تأثير التخطي (تبعية مصفوفة فارغة)


في المثال أدناه ، تم تمرير useEffect إلى مصفوفة فارغة كتبعية ، وبالتالي لن يعمل التأثير إلا على التصيير الأولي.



الرمز:



const SkipEffect = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, []);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


Sandbox:







عند النقر فوق الزر ، لا يتم استدعاء useEffect.



تأثير التخطي (بدون تبعيات)


إذا لم يكن هناك مصفوفة تبعية ، فسيتم تشغيل التأثير في كل مرة يتم فيها عرض الصفحة.



useEffect(() => {
  console.log(
    "        ."
  );
});


useContext



يزيل useContext الحاجة إلى الاعتماد على سياق المستهلك. يحتوي على واجهة أبسط مقارنةً بـ MyContext.Consumer و Props rendering. فيما يلي مقارنة لاستخدام السياق باستخدام useContext و Context.Consumer.



//    Context
const ThemeContext = React.createContext("dark")

//   
function Button() {
    return (
        <ThemeContext.Consumer>
            {theme => <button className={thene}> !</button>}
        </ThemeContext.Consumer>
}

//  useContext
import { useContext } from "react"

function ButtonHook() {
    const theme = useContext(ThemeContext)
    return <button className={theme}> !</button>
}


الرمز:



const ChangeTheme = () => {
  const [mode, setMode] = useState("light");

  const handleClick = () => {
    setMode(mode === "light" ? "dark" : "light");
  };

  const ThemeContext = React.createContext(mode);

  const theme = useContext(ThemeContext);

  return (
    <div
      style={{
        background: theme === "light" ? "#eee" : "#222",
        color: theme === "light" ? "#222" : "#eee",
        display: "grid",
        placeItems: "center",
        minWidth: "320px",
        minHeight: "320px",
        borderRadius: "4px",
      }}
    >
      <p> : {theme}.</p>
      <button onClick={handleClick}>  </button>
    </div>
  );
};


Sandbox:





useLayoutEffect



يشبه سلوك useLayoutEffect سلوك useEffect ، مع بعض الاستثناءات التي سنتحدث عنها لاحقًا.



  useLayoutEffect(() => {
    // 
  }, []);


الاستخدام الأساسي


هذا مثال باستخدام useEffect ، لكن مع useLayoutEffect.



الرمز:



  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useLayoutEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


Sandbox:





useLayoutEffect and useEffect


يتم استدعاء الوظيفة التي تم تمريرها إلى useEffect بعد عرض الصفحة ، أي بعد تشكيل التخطيط وتقديم العناصر. هذا مناسب لمعظم التأثيرات الإضافية التي لا ينبغي أن تعيق التدفق. ومع ذلك ، إذا كنت تريد على سبيل المثال القيام ببعض التلاعب في DOM كتأثير إضافي ، فإن useEffect ليس هو الخيار الأفضل. لمنع المستخدم من رؤية التغييرات ، يجب استخدام useLayoutEffect. يتم استدعاء الوظيفة التي تم تمريرها إلى useLayoutEffect قبل تقديم الصفحة.



useReducer



useReducer يمكن استخدامها كبديل للuseState، ومع ذلك، والغرض منه هو لتغليف منطق معقد للعمل مع الدول، عندما تعتمد الدولة على القيمة السابقة أو هناك العديد من الدول.



الاستخدام الأساسي


في المثال أدناه ، يتم استخدام useReducer بدلاً من useState. ترجع استدعاء useReducer قيمة حالة ودالة إرسال.



الرمز:



const initialState = { width: 30 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const BasicReducer = () => {
  const [state, dispath] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


Sandbox:





تأجيل ("كسول") تهيئة الحالة


تأخذ useReducer وسيطة اختيارية ثالثة ، وهي دالة تُرجع كائن حالة. تسمى هذه الوظيفة مع initialState كوسيطة ثانية.



الرمز:



const initializeState = () => ({
  width: 90,
});

//  ,  initializeState   
const initialState = { width: 0 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const LazyState = () => {
  const [state, dispath] = useReducer(reducer, initialState, initializeState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


Sandbox:





محاكاة سلوك this.setState


useReducer يستخدم مخفض أقل صرامة من Redux. على سبيل المثال ، لا تحتاج الوسيطة الثانية التي تم تمريرها إلى المخفض إلى خاصية النوع. هذا يوفر لنا فرصا مثيرة للاهتمام.



الرمز:



const initialState = { width: 30 };

const reducer = (state, newState) => ({
  ...state,
  width: newState.width,
});

const NewState = () => {
  const [state, setState] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => setState({ width: 300 })}>
          .
      </button>
      <button onClick={() => setState({ width: 30 })}>
          .
      </button>
    </>
  );
};


Sandbox:





useCallback



يعيد useCallback رد الاتصال المحفوظ (المخزن مؤقتًا).



نموذج المبتدئين


الرمز:



const CallbackTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Sandbox:







في المثال أعلاه ، يتم تحديث مكون Age وإعادة عرضه عند الضغط على الزر. يُعاد تصيير مكوِّن الدليل أيضًا عندما يتم تمرير رد نداء جديد إلى خاصيات doSomething. على الرغم من أن الدليل يستخدم React.memo لتحسين الأداء ، إلا أنه لا يزال يتم إعادة رسمه. كيف يمكننا إصلاح هذا؟



الاستخدام الأساسي


الرمز:



const BasicCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = useCallback(() => someValue, [someValue]);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Sandbox:







استخدام مدمج


يمكن استخدام useCallback كوظيفة مضمنة.



الرمز:



const InlineCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={useCallback(() => someValue, [someValue])} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Sandbox:





useMemo



تُرجع useMemo القيمة المخزنة (المخزنة مؤقتًا).



نموذج المبتدئين


الرمز:



const MemoTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = { value: "some value" };
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Sandbox:





هذا القالب مطابق لقالب useCallback المبدئي ، فيما عدا أن someValue هي كائن وليس سلسلة. يتم أيضًا إعادة تقديم مكون الدليل على الرغم من استخدام React.memo.



ولكن لماذا يحدث هذا؟ بعد كل شيء ، تتم مقارنة الكائنات بالمرجع ، وتتغير الإشارة إلى بعض القيمة مع كل عرض. أيه أفكار؟



الاستخدام الأساسي


يمكن تخزين القيمة التي تم إرجاعها بواسطة doSomething باستخدام useMemo. سيمنع هذا العرض غير الضروري.



الرمز:



const BasicMemo = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = () => ({ value: "some value" });
  const doSomething = useMemo(() => someValue, []);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Sandbox:





الاستخدام


تعيد useRef كائن المرجع. قيم هذا الكائن متاحة من خلال الخاصية "الحالية". يمكن تعيين قيمة أولية لهذه الخاصية: useRef (initialValue). كائن المرجع موجود لحياة المكون.



الوصول إلى DOM


الرمز:



const DomAccess = () => {
  const textareaEl = useRef(null);
  const handleClick = () => {
    textareaEl.current.value =
      " - ,     . ,    !";
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
                 .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


Sandbox:





المتغيرات الشبيهة بالمثيل (الأدوية)


يمكن أن يحتوي كائن المرجع على أي قيمة ، وليس مجرد مؤشر لعنصر DOM.



الرمز:



const StringVal = () => {
  const textareaEl = useRef(null);
  const stringVal = useRef(
    " - ,     . ,    !"
  );
  const handleClick = () => {
    textareaEl.current.value = stringVal.current;
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
               .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


Sandbox:





يمكن استخدام useRef لتخزين معرف المؤقت بحيث يمكن إيقافه لاحقًا.



الرمز:



const IntervalRef = () => {
  const [time, setTime] = useState(0);
  const setIntervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      setTime((time) => (time = new Date().toLocaleTimeString()));
    }, 1000);

    setIntervalRef.current = id;

    return () => clearInterval(setIntervalRef.current);
  }, [time]);

  return (
    <>
      <p> :</p>
      <time>{time}</time>
    </>
  );
};


Sandbox:





أرجو أن تتمتع هذه المادة. شكرآ لك على أهتمامك.



All Articles