ساخت کامپوننت Tooltip با قلاب React — از صفر تا صد
امروزه هر کس که با ریاکت کدنویسی میکند، قطعاً با ویژگی قلابها در این فریمورک آشنا شده است. در این مقاله قصد داریم با روش ساخت کامپوننت Tooltip با استفاده از قلابهای React آشنا شویم. چیزی که میخواهیم بسازیم را در تصویر زیر میتوانید ببینید:
ساختار کامپوننت
Tooltip عموماً برای ارائه اطلاعاتی در مورد یک چیز به خصوص در زمانی که اشارهگر ماوس روی آن عنصر قرار میگیرد، استفاده میشود. با این حال در این راهنما یک رویداد کلیک را به جای آن جایگزین میکنیم تا با شیوه مدیریت کلیکهای inside و outside نیز آشنا شوید.
ساختار کامپوننتی که میخواهیم بسازیم به صورت زیر است:
1<Tooltip title="Tooltip on top" position="top">
2 <button>Tooltip on top</button>
3</Tooltip>
این کامپوننت سه props به نامهای title ،position و یک فرزند منفرد به نام button دارد که در تصویر فوق نمایش یافتهاند. Prop به نام child میتواند هر چیزی که یک orphan است باشد.
- Title محتوای tooltip را نگهداری میکند.
- Position موقعیتهای مختلف tooltip یعنی top | right | left | bottom را ذخیره میسازد.
- Button یک عنصر قابل کلیک است که محتوای بیشتر را نمایش میدهد.
در ادامه فایل App را ایجاد میکنیم که کامپوننت Tooltip را ایمپورت کرده و مورد استفاده قرار میدهد:
1// Externals
2import React from 'react';
3// Internals
4import Tooltip from './Tooltip/Tooltip';
5function App () {
6 return (
7 <section>
8 <h1>Tooltips using React Hooks</h1>
9 <Tooltip title="Tooltip on top" position="top">
10 <button>Tooltip on top</button>
11 </Tooltip>
12 <Tooltip title="Tooltip on bottom" position="bottom">
13 <button>Tooltip on Bottom</button>
14 </Tooltip>
15 <Tooltip title="Tooltip on left" position="left">
16 <button>Tooltip on left</button>
17 </Tooltip>
18 <Tooltip title="Tooltip on right" position="right">
19 <button>Tooltip on right</button>
20 </Tooltip>
21 </section>
22 );
23}
24export default App;
کامپوننت خود را چهار بار رندر میکنیم تا همه موقعیتهای مختلف را پوشش دهد و آنها را در عمل بررسی کنیم. کد فوق کار نخواهد کرد، زیرا یک چنین فایل Tooltip وجود ندارد. در بخش بعدی این فایل را میسازیم.
منطق کامپوننت
قلابهای لازم را از React ایمپورت میکنیم:
1// Externals
2import React, { useEffect, useRef, useState } from 'react';
در کد فوق:
- useEffect مشابه componentDidMount و componentDidUpdate است.
- useRef یک شیء ref به عنصر بازگشت میدهد.
- useState یک stateful value و یک function برای بهروزرسانی آن بازگشت میدهد.
در ادامه به بررسی منطق کامپوننت میپردازیم:
1const node = useRef();
2const [isVisible, setState] = useState(false);
3const handleClick = ({ target }) => {
4 if (node.current.contains(target)) {
5 // inside click
6 return;
7 }
8 // outside click
9 setState(false);
10};
در کد فوق متدهای مختلف که مسئول منطق کامپوننت هستند را در رابطه با هر عنصر مقداردهی میکنیم. این کار به لطف ()useRef انجام مییابد که یک شیء ref تغییرپذیر از آن عنصر بازگشت میدهد. ()handleClick مسئولیت کلیکهای inside و outside مربوط به عنصر نصب شده current را بر عهده میگیرد.
برای فراخوانی متد ()handleClick باید آن را به یک «شنونده رویداد» (event listener) وصل کنیم. از آنجا که از قلابها استفاده میکنیم، این فراخوانی باید درون ()useEffect به صورت زیر قرار گیرد:
1useEffect(() => {
2 // add when mounted
3 document.addEventListener('mousedown', handleClick);
4 // return function to be called when unmounted
5 return () => {
6 document.removeEventListener('mousedown', handleClick);
7 };
8}, []);
متد ()useEffect به مدیریت هر دو متد چرخه عمری ()componentDidMount و ()componentWillUnmount میپردازد و از این جهت میتوانیم حتی event listener را در زمانی که کامپوننت unmount شده یا از DOM حذف میشود تخریب کنیم. اینک کار تقریباً به پایان رسیده است. اکنون زمان پرداختن به متد رندر رسیده است. به بیان دیگر اینک باید لیآوت کامپوننت را بازگشت دهیم. ما از کامپوننت مبتنی بر کلاس استفاده نمیکنیم، زیرا به لطف قلابها چیزی به نام متد render وجود ندارد.
1return (
2 <div className={Styles.container}
3 data-testid="tooltip"
4 ref={node}
5 onClick={() => setState(!isVisible)}
6 >
7 <div data-testid="tooltip-placeholder">{children}</div>
8 {isVisible && (
9 <div
10 className={cx(Styles.tooltipContent, Styles[position])}
11 data-testid="tooltip-content"
12 >
13 <span className={Styles.arrow}></span>
14 {title}
15 </div>
16 )}
17 </div>
18);
توجه کنید که data-testid صرفاً به منظور تست کردن ارائه شده است و شما به آن نیازی نخواهید داشت. محتوای Tooltip تنها در صورتی نمایان خواهد بود که isVisible به صورت true باشد و زمانی که خارج از آن کلیک شود به صورت False درمیآید.
برای این که کامپوننت، انعطافپذیری بیشتری داشته باشد، یک مقدار پیشفرض برای prop مربوط به position تعیین میکنیم تا اگر مقداری تعریف نشده باشد، در سمت راست عنصر قرار گیرد. این مورد را میتوانیم به سادگی با استفاده از propTypes پیادهسازی کنیم:
1Tooltip.defaultProps = {
2 position: 'right',
3};
وقتی از propTypes صحبت میکنیم، باید مطمئن شویم که نوع props را به درستی بررسی کردهایم تا از بروز موارد غیرمترقبه پیشگیری کنیم. این شیء را میتوانیم به صورت زیر تعریف کنیم:
1const propTypes = {
2 title: PropTypes.string.isRequired,
3 position: PropTypes.string,
4 children: PropTypes.node.isRequired,
5};
در کد فوق title و children الزامی هستند. در نهایت میتوانیم هر دو کامپوننت propTypes و Tooltip را اکسپورت کنیم:
1Tooltip.propTypes = propTypes;
2export default Tooltip;
فایل نهایی به صورت زیر است:
1// Externals
2import React, { useEffect, useRef, useState } from 'react';
3import PropTypes from 'prop-types';
4import cx from 'classnames';
5// Styling
6import Styles from './styles.css';
7
8const propTypes = {
9 title: PropTypes.string.isRequired,
10 position: PropTypes.string,
11 children: PropTypes.node.isRequired,
12};
13
14const Tooltip = ({ title, position, children }) => {
15 const node = useRef();
16 const [isVisible, setState] = useState(false);
17 const handleClick = ({ target }) => {
18 if (node.current.contains(target)) {
19 // inside click
20 return;
21 }
22 // outside click
23 setState(false);
24 };
25
26 useEffect(() => {
27 // add when mounted
28 document.addEventListener('mousedown', handleClick);
29 // return function to be called when unmounted
30 return () => {
31 document.removeEventListener('mousedown', handleClick);
32 };
33 }, []);
34
35 return (
36 <div className={Styles.container}
37 data-testid="tooltip"
38 ref={node}
39 onClick={() => setState(!isVisible)}
40 >
41 <div data-testid="tooltip-placeholder">{children}</div>
42 {isVisible && (
43 <div
44 className={cx(Styles.tooltipContent, Styles[position])}
45 data-testid="tooltip-content"
46 >
47 <span className={Styles.arrow}></span>
48 {title}
49 </div>
50 )}
51 </div>
52 );
53};
54
55Tooltip.defaultProps = {
56 position: 'right',
57};
58
59Tooltip.propTypes = propTypes;
60
61export default Tooltip;
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی فریمورک React Native
- اضافه کردن بررسی نوع به کامپوننت React — از صفر تا صد
- ارسال پارامترهای چندگانه مسیر در React — به زبان ساده
==