پیاده‌سازی مکانیسم حذف < li > ها

implementing &lt;li&gt; Remove Mechanism

22 بهمن 1399
پیاده سازی مکانیسم حذف  ها

در قسمت قبل در مورد تغییر State به صورت immutable صحبت کردیم و قابلیت ذخیره سازی عدد فعلی شمارنده را به پروژه اضافه کردیم اما هنوز هیچ کدی برای action حذف <li> ها ننوشته ایم. ما از قسمت ساده کار شروع می کنیم: دریافت Action در reducer.js و اضافه کردن یک case جدید به دستور switch! اما مشکلی وجود دارد:

case 'DELETE_RESULT':
        return {
            ...state,
            // در این قسمت چه چیزی بنویسیم؟
        }

برای اضافه کردن یک آیتم به آرایه ها به صورت immutable از concat استفاده می کنیم اما برای حذف آن ها چه کار کنیم؟ در حالت عادی برای حذف یک آیتم از یک آرایه باید index آن را به عنوان پارامتر اول و تعداد عناصری که باید حذف شوند را به عنوان پارامتر دوم به تابع splice بدهیم. به طور مثال دستور زیر فقط آیتم با index دو را حذف می کند (index دو را پیدا می کند و به تعداد پارامتر دوم حذف می کند که یعنی فقط یک آیتم):

array.splice(2, 1);

مشکل اینجاست که این روش به صورت immutable نیست و آرایه اصلی را دستکاری می کند. بنابراین تنها راه حل ما کپی کردن آرایه است. به طور مثال:

cons newArray = [...state.results]

سپس می توانیم دستور Splice را روی این آرایه جدید صدا بزنیم. اگر یادتان باشد گفتم که اپراتور Spread اشیاء را deep clone نمی کند یعنی اگر درون شیء یک شیء دیگر داشته باشیم، شیء داخلی مستقلا کپی نمی شود بلکه یک reference به همان شیء اصلی خواهد بود (مبحث reference based بودن آرایه ها و اشیاء در جاوا اسکرپت که در جلسات اول توضیح داده بودیم). به مثال زیر نگاه کنید:

const id = 2;
const newArray = [...state.results];

متغیر newArray در بالا یک کپی مستقل از آرایه results درون state است اما همانطور که می دانیم آرایه results دارای اشیاء مختلف است:

case 'STORE_RESULT':
        return {
            ...state,
            results: state.results.concat({ id: new Date(), value: state.counter })
        }

کد بالا را در جلسه قبل نوشتیم. مشخص است که با هر بار کلیک روی دکمه Store Result یک شیء جدید به آرایه results اضافه می شود و این آرایه دو بعدی می شود. مثلا چیزی شبیه به آرایه فرضی زیر:

const results = [
    {
        person: {
            name: 'Amir',
            sirName: 'Zouerami'
        },
        website: {
            url: 'roxo.ir',
            field: 'programming'
        }
    },
    {
        person: {
            name: 'Mohammad',
            sirName: 'Golshani'
        },
        website: {
            url: 'exampleSite.ir',
            field: 'Cinema'
        }
    }

]

در آرایه بالا یک سطح person و website هستند و در سطح بعدی نیز url و field را داریم بنابراین آرایه چند بعدی است. deep clone نشدن محتویات results (در پروژه خودمان) یعنی اشیاء داخل آن هنوز هم به شیء state اصلی اشاره می کنند (reference آن هستند) و شیء اصلی را تغییر می دهند که immutable نیست. در واقع با استفاده از پراتور spread فقط تا یک مرحله وارد شیء/آرایه شده و آن را کپی می کنیم، اگر درون آن شیء/آرایه یک یا چند شیء/آرایه دیگر وجود داشته باشد، آن ها دیگر کپی نمی شوند چرا که در بعد دوم یا بعد های بعدی هستند.

بنابراین کپی کردن آرایه results به شکل زیر برای تغییر دادن آن کافی نیست:

const newArray = [...state.results];

اما برای حذف کردن اشیاء کافی است! چرا که اصلا شیء را ویرایش نمی کنیم بلکه آن را حذف خواهیم کرد و حذف کامل یک شیء نیز در سطح اول است. البته من به جای روش بالا از روش دیگری استفاده می کنم. تابعی به نام filter وجود دارد که اعضای آرایه را فیلتر کرده و یک آرایه جدید برمی گرداند. filter به عنوان پارامتر خود یک تابع می گیرد که آن را روی تک تک اعضا اجرا می کند و شما باید تابع را طوری بنویسید که بر اساس شرطی خاص true را False را برگرداند. بر همین اساس اعضایی که true بگیرند در آرایه جدید مانده و اعضایی که false بگیرند حذف خواهند شد. حالا اگر بدون هیچ شرطی برای همه اعضا True برگردانیم، یک کپی کامل از آرایه اصلی ایجاد کرده ایم:

const updatedArray = state.results.filter(result => true);

اما ما می توانیم کاری کنیم که عملیات را برایمان آسان تر کند. ما می توانیم تابع را طوری بنویسیم که فقط برای عناصری که دارای id حذف شده نیستند مقدار true را برگرداند تا بدین شکل فقط عنصر مورد نظر ما حذف شود:

const updatedArray = state.results.filter((result, index) => index !== id);

در کد بالا دو پارامتر داریم: یکی result که تک تک اعضای آرایه state.results هستند و دیگری index که قرار است index عنصر مورد نظر ما باشد. سپس گفته ایم اگر index هر عنصر برابر با id نبود (id قرار است index عنصری باشد که می خواهیم آن را حذف کنیم) مقدار true برگردانده شود.

بر اساس آنچه گفتم برای پروژه خودمان می توانیم به شکل زیر عمل کنیم:

case 'DELETE_RESULT':
        const updatedArray = state.results.filter(result => result.id !== action.resultElId);
        return {
            ...state,
            results: updatedArray
        }

من با استفاده از filter روی تمام اعضای آرایه state.results گردش کرده ام و گفته ام برای هر عنصری که result.id آن (همان new Date که برایشان گذاشتیم) برابر با action.resultElId نباشد مقدار true را برگردان. در واقع action.resultElId قرار است id عنصری باشد که روی آن کلیک شده و قرار است حذف شود بنابراین باید به Counter.js رفته و آن را با action خود پاس بدهیم:

const mapDispatchToProps = dispatch => {
    return {
        onIncrementCounter: () => dispatch({ type: 'INCREMENT' }),
        onDecrementCounter: () => dispatch({ type: 'DECREMENT' }),
        onAddCounter: () => dispatch({ type: 'ADD', val: 10 }),
        onSubtractCounter: () => dispatch({ type: 'SUBTRACT', val: 15 }),
        onStoreResult: () => dispatch({ type: 'STORE_RESULT' }),
        onDeleteResult: (id) => dispatch({ type: 'DELETE_RESULT', resultElId: id })
    };
};

من یک مقدار به نام دلخواه id را به این تابع پاس داده ام (توجه کنید که باید id را به عنوان پارامتر پاس بدهید) اما فعلا تابع من چنین مقداری را دریافت نمی کند. یعنی چیزی به نام id برای آن بی معنی است. برای حل این مشکل نیز به قسمت JSX می رویم و id را از آنجا پاس می دهیم:

render() {
    return (
        <div>
            <CounterOutput value={this.props.ctr} />
            <CounterControl label="Increment" clicked={this.props.onIncrementCounter} />
            <CounterControl label="Decrement" clicked={this.props.onDecrementCounter} />
            <CounterControl label="Add 10" clicked={this.props.onAddCounter} />
            <CounterControl label="Subtract 15" clicked={this.props.onSubtractCounter} />
            <hr />
            <button onClick={this.props.onStoreResult}>Store Result</button>
            <ul>
                {this.props.storedResults.map(strResult => (
                    <li key={strResult.id} onClick={() => this.props.onDeleteResult(strResult.id)}>{strResult.value}</li>
                ))}
            </ul>
        </div>
    );
}

اگر دقت کنید id هر عنصر را به همراه <li> پاس داده ام. حالا می توانید به مرورگر رفته و کدها را تست کنید. می بینیند که با کلیک روی هر کدام از <li> ها می توانیم آن ها را حذف کنیم. امیدوارم نکات مهم این جلسه را کاملا درک کرده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.