مدیریت خطاها در حین ارسال درخواستهای HTTP به سمت سرور، همیشه یک الزام است. این دقیقا کاری است که میخواهیم در این مقاله انجام دهیم. ما میخواهیم اگر سرور یک خطا با کد وضعیت 404 یا 500 برگرداند، کاربر را به یک صفحه ویژه هدایت کنیم. رسیدگی به خطاهایی که دارای کدهای وضعیتی غیر از 404 یا 500 هستند با استفاده از component های modal form اجرا می شوند. ما هم اکنون، برای خطای Not Found (404) یک صفحه داریم، بنابراین اجازه دهید کار خود را با ایجاد کامپوننت Internal Server Error (500) ادامه دهیم.
اگر میخواهید تمام دستورالعملهای پایه و راهنمای کامل برای آموزش سریالی NET Core. را ببینید، این لینک را بررسی کنید: دستورالعملهای کار با NET Core.
این مقاله، قسمتی از مجموعه آموزشی زیر میباشد:
- آماده سازی پروژه و ایجاد component ها
- Navigation و مسیریابی (Routing)
- Axios ،HTTP و Redux
- Lazy Loading و HOC Component
- مدیریت خطا و component های اضافی
- ایجاد form داینامیک و component های modal
- اعتبارسنجی Form و Handle کردن درخواست Post
- مدیریت درخواست PUT
- مدیریت درخواست DELETE
اگر میخواهید تمام دستورالعملهای پایه و راهنمای کامل برای آموزش سریالی React را ببینید، به این لینک مراجعه کنید: مقدمه معرفی آموزش سریالی React
برای بررسی قسمت قبل، به این لینک مراجعه کنید: Lazy Loading و HOC Component
جهت دانلود سورس بر روی این لینک کلیک کنید: سری آموزشی React – قسمت 5
این مقاله به قسمتهای زیر تقسیم میشود:
- ایجاد کامپوننت 500 (Internal Server Error)
- پیاده سازی Redux برای مدیریت خطا
- ثبت Reducer ها به صورت ترکیبی
- اصلاح کامپوننت OwnerList
- پیاده سازی کامپوننت OwnerDetails
- نتیجه گیری
ایجاد کامپوننت 500 (Internal Server Error)
اجازه دهید یک پوشه جدید به نام InternalServer
داخل پوشه ErrorPages
ایجاد کنیم و در این پوشه، دو فایل جدید به نامهای InternalServer.js
و InternalServer.css
ایجاد کنیم:
ما قصد داریم فایل InternalServer.js را به صورت زیر اصلاح کنیم:
1 2 3 4 5 6 7 8 9 10 |
import React from 'react'; import './InternalServer.css'; const internalServer = (props) => { return ( <p className={'internalServer'}>{"500 SERVER ERROR, CONTACT ADMINISTRATOR!!!!"}</p> ) } export default internalServer; |
سپس فایل InternalServer.css را اصلاح میکنیم:
1 2 3 4 5 6 |
.internalServer{ font-weight: bold; font-size: 50px; text-align: center; color: #c72d2d; } |
در آخر، فایل App.js را اصلاح میکنیم:
1 |
import InternalServer from '../components/ErrorPages/InternalServer/InternalServer'; |
1 2 3 4 |
<Route path="/" exact component={Home} /> <Route path="/owner-list" component={AsyncOwnerList} /> <Route path="/500" component={InternalServer} /> <Route path="*" component={NotFound} /> |
عالی.
کامپوننت Internal Server Error (500) در حال حاضر آماده است و حالا به کار خود ادامه میدهیم.
پیاده سازی Redux برای مدیریت خطا
همان کاری که با قسمت repository این اپلیکیشن انجام دادیم، میخواهیم یک نمونه redux دیگر برای رسیدگی به خطاها در یک مکان واحد در اپلیکیشن خود ایجاد کنیم. ما با جریان redux آشنا هستیم، بنابراین پیاده سازی این قسمت بسیار آسان است. یک چیز دیگری که بعد از ایجاد این فایل reducer جدید باید به آن بپردازیم، این است که با این فایل جدید، قرار است دو فایل reducer داشته باشیم و بنابراین باید یک فرآیند متفاوتی از ثبت را داخل فایل Index.js انجام دهیم.
ما میتوانیم خطاها را به شیوه های مختلفی handle کنیم و ما قصد داریم این کار را با استفاده از چند reducer در یک پروژه انجام دهیم. علاوه بر این، متمرکز سازی اقدامات رسیدگی به خطا به طور کلی یک شیوه خوب به حساب می آید.
حال بیایید با اصلاح فایل ActionTypes.js با افزودن سه نوع action دیگر، به کار خود ادامه دهیم:
1 2 3 |
export const HTTP_404_ERROR = 'HTTP_404_ERROR'; export const HTTP_500_ERROR = 'HTTP_500_ERROR'; export const HTTP_OTHER_ERROR = 'HTTP_OTHER_ERROR'; |
داخل پوشه actions
، ما فایل repositoryActions.js را داریم. حالا داخل همین پوشه، باید فایل دیگری به نام errorHandlerActions.js را ایجاد کنیم. بنابراین این فایل را به صورت زیر اصلاح میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import * as actionTypes from './actionTypes'; const execute404Handler = (props) => { return { type: actionTypes.HTTP_404_ERROR, props: props } } const execute500Handler = (props) => { return { type: actionTypes.HTTP_500_ERROR, props: props } } const executeOtherErrorHandler = (error) => { return { type: actionTypes.HTTP_OTHER_ERROR, error: error } } export const handleHTTPError = (error, props) => { if (error.response.status === 404) { return execute404Handler(props); } else if (error.response.status === 500) { return execute500Handler(props); } else { return executeOtherErrorHandler(error); } } |
در کد بالا ما اکشن handleHTTPError
را export کرده ایم که در آن status code خطا را بررسی کرده و function مربوطه را اجرا میکنیم. این همان کاری است که ما با فایل repositoryActions.js قبلا انجام داده بودیم.
درون فایل repositoryActions.js، باید این فایل errorHandlerActions.js را import کنیم:
1 |
import * as errorHandlerActions from './errorHandlerActions'; |
و تمام comment های درون بلاک catch مربوط به هر function را با کد زیر جایگزین کنید:
1 |
dispatch(errorHandlerActions.handleHTTPError(error, props)); |
حال اجازه دهید با ایجاد یک فایل reducer جدید به نام errorHandlerReducer.js در داخل پوشه reducers به کار خود ادامه دهیم.
حالا این ساختار پوشه ای زیر را در اختیار داریم:
فایل errorHandlerReducer.js باید به شکل زیر باشد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import * as actionTypes from '../actions/actionTypes'; const initialState = { showErrorModal: false, errorMessage: '' } const execute404 = (state, action) => { action.props.history.push('/404'); return { ...state }; } const execute500 = (state, action) => { action.props.history.push('/500'); return { ...state }; } const executeOtherError = (state, action) => { return { ...state, showErrorModal: true, errorMessage: action.error.response.data }; } const reducer = (state = initialState, action) => { switch (action.type) { case actionTypes.HTTP_404_ERROR: return execute404(state, action); case actionTypes.HTTP_500_ERROR: return execute500(state, action); case actionTypes.HTTP_OTHER_ERROR: return executeOtherError(state, action); default: return state; } } export default reducer; |
این logic نیز آشنا به نظر میرسد. ما اینجا آبجکت state (initialState
) و سپس تابع reducer
که پارامترهای state
و action
را به عنوان ورودی میگیرد را ایجاد کردیم. تابع reducer
قصد دارد state
را بر اساس ویژگی type ارسالی از فایل errorHandlerActions
بروزرسانی کند.
ثبت Reducer ها به صورت ترکیبی
برای اتمام تنظیم Redux، فایل Index.js را برای ثبت reducer نیز اصلاح میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import repositoryReducer from './store/reducers/repositoryReducer'; import errorHandlerReducer from './store/reducers/errorHandlerReducer'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware, combineReducers } from 'redux'; import thunk from 'redux-thunk'; const rootReducers = combineReducers({ repository: repositoryReducer, errorHandler: errorHandlerReducer }) const store = createStore(rootReducers, applyMiddleware(thunk)); ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root')); registerServiceWorker(); |
تمام. ما اینجا فایل errorHandlerReducer
و تابع combineReducers
را import کرده ایم. سپس با کمک تابع combineReducers
، آبجکت rootReducers
که شامل تمام reducer های ما است را ایجاد میکنیم. در آخر، این آبجکت را به createStore پاس میدهیم.
اصلاح کامپوننت OwnerList
ما یک کار دیگر نیز باید انجام دهیم. در کامپوننت OwnerList، باید شیوه تنظیم ویژگی data
از فایل repositoryReducer
را تغییر دهیم. پس تابع mapStateToProps
را اصلاح میکنیم:
1 2 3 4 5 |
const mapStateToProps = (state) => { return { data: state.repository.data } } |
ما حتما باید این کار را انجام دهیم چرا که دیگر فقط یک فایل reducer نداریم. هر دو فایلهای reducer ما، داخل یک آبجکت root ثبت شده اند و بنابراین باید مشخص کنیم که کدام reducer را میخواهیم استفاده کنیم.
عالی.
ما میتوانیم با تغییر دادن کد متد GetAllOwners سمت سرور، مدیریت خطای خود را امتحان کنیم. به عنوان اولین خط از کد، میتوانیم ()return NotFound
یا return StatusCode(500, “Some message”)
را اضافه کنیم که در این صورت، قطعا به صفحه درست خطای مربوطه هدایت میشویم.
پیاده سازی کامپوننت OwnerDetails
در این component، ما قصد داریم owner مورد نظر را به همراه تمام account های آن نمایش دهیم. اگر شما اینطور فکر میکنید: “خب ما آن را به دو component جداگانه تقسیم میکنیم”، کاملا حق با شماست.
ما دقیقا همین کار را میخواهیم انجام دهیم.
component والد قرار است کامپوننت OwnerDetails باشد و کامپوننت OwnersAccounts نیز قرار است کامپوننت فرزند باشد. بنابراین اجازه دهید اول کار خود را با ایجاد کامپوننت فرزند شروع کنیم.
برای کامپوننت OwnersAccounts، میخواهیم این ساختار را ایجاد کنیم:
فایل OwnersAccounts.js را به صورت زیر اصلاح کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import React from 'react'; import { Row, Col, Table } from 'react-bootstrap'; import Moment from 'react-moment'; const ownersAccounts = (props) => { let accounts = null; if (props.accounts) { accounts = props.accounts.map(account => { return ( <tr key={account.id}> <td>{account.accountType}</td> <td><Moment format="DD/MM/YYYY">{account.dateCreated}</Moment></td> </tr> ); }) } return ( <Row> <Col md={12}> <Table responsive striped> <thead> <tr> <th>Account type</th> <th>Date created</th> </tr> </thead> <tbody> {accounts} </tbody> </Table> </Col> </Row> ) } export default ownersAccounts; |
ما تا اینجا کار خود را عالی انجام داده ایم.
حال برای کامپوننت OwnerDetails، ساختار پوشه ای زیر را در نظر میگیریم:
سپس تمام فایلهای ضروری را داخل کامپوننت OwnerDetails ایمپورت میکنیم:
1 2 3 4 5 6 7 |
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { Well, Row, Col } from 'react-bootstrap'; import * as repositoryActions from '../../../store/actions/repositoryActions'; import Moment from 'react-moment'; import OwnersAccounts from '../../../components/OwnerComponents/OwnersAccounts/OwnersAccounts'; import Aux from '../../../hoc/Auxiliary/Auxiliary'; |
و بعد از این، component را به صورت زیر پیاده سازی میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class OwnerDetails extends Component { render() { const owner = this.props.data; return ( <Aux> <Well> <Row> <Col md={3}> <strong>Owner name:</strong> </Col> <Col md={3}> {owner.name} </Col> </Row> <Row> <Col md={3}> <strong>Date of birth:</strong> </Col> <Col md={3}> <Moment format="DD/MM/YYYY">{owner.dateOfBirth}</Moment> </Col> </Row> {this.renderTypeOfUserConditionally(owner)} </Well> <OwnersAccounts accounts={owner.accounts} /> </Aux> ) } } export default OwnerDetails; |
در کد بالا ما از عبارت this.props.data استفاده کرده ایم، اما هنوز reducer را پیاده سازی نکرده ایم. ما می خواهیم این کار را در یک دقیقه انجام دهیم. به فراخوانی تابع renderTypeOfUserConditionally توجه کنید. در این تابع، دادههای owner را به صورت مشروط رندر میکنیم و کد JSX را برای نمایش برمیگردانیم. ما این تابع را نیز قصد داریم در یک دقیقه پیاده سازی کنیم. زیر داده های owner، ما تمام account های مربوط به owner مورد نظر را نمایش میدهیم.
رندر کردن به صورت شرطی
برای پیاده سازی renderTypeOfUserConditionally، باید کد زیر را بالای تابع render
اما همچنان داخل class اضافه کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
renderTypeOfUserConditionally = (owner) => { let typeOfUser = null; if (owner.accounts && owner.accounts.length <= 2) { typeOfUser = ( <Row> <Col md={3}> <strong>Type of user:</strong> </Col> <Col md={3}> <span className={'text-success'}>Beginner user.</span> </Col> </Row> ); } else { typeOfUser = ( <Row> <Col md={3}> <strong>Type of user:</strong> </Col> <Col md={3}> <span className={'text-info'}>Advanced user.</span> </Col> </Row> ); } return typeOfUser; } |
اتصال Redux
در آخر، به صورت زیر، این کامپوننت OwnerDetails را به فایل reducer برای واکشی داده های owner زیر تگ بسته تابع render متصل میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const mapStateToProps = (state) => { return { data: state.repository.data } } const mapDispatchToProps = (dispatch) => { return { onGetData: (url, props) => dispatch(repositoryActions.getData(url, props)) } } export default connect(mapStateToProps, mapDispatchToProps)(OwnerDetails); |
و بالای تابع renderTypeOfUserConditionally
، این lifecycle hook را اضافه میکنیم:
1 2 3 4 5 |
componentDidMount = () => { let id = this.props.match.params.id; let url = '/api/owner/' + id + '/account'; this.props.onGetData(url, { ...this.props }) } |
ما با فراخوانی عبارت match.params.id از آبجکت
،id
props
را از url بیرون میکشیم. سپس تنها url خود را ایجاد کرده و ویژگی onGetData
را برای واکشی داده ها از سرور فراخوانی میکنیم.
قبل از بررسی نتیجه، باید route مورد نظر برای این component جدید را در فایل App.js، درست زیر owner-list
route اضافه کنیم:
1 |
<Route path="/ownerDetails/:id" component={OwnerDetails} /> |
در آخر، میتوانیم نتیجه را برای کاربر Beginner بررسی کنیم:
علاوه بر این، نتیجه را برای کاربر Advanced نیز بررسی میکنیم:
عالی. همه چیز تا اینجا خوب پیش رفت.
ما میتوانیم این نتایج را بررسی کنیم و مطمئن شویم که همه چیز طبق انتظار کار خواهد کرد.
نتیجه گیری
در حال حاضر، ما کاملا مطمئن هستیم که پیاده سازی Redux برای شما خیلی راحت است. همانطور که در مقاله های قبلی بیان کردیم، پس از مدتی تمرین، می توانید روند Redux را با استفاده از الگوی پیاده سازی منحصر به خودش به راحتی پیاده سازی کنید. تا اینجا ما از آن برای ارسال درخواستهای HTTP و رسیدگی به خطاهای HTTP استفاده کردیم، و اکنون در هر task که خودتان بخواهید، میتوانید آن را تمرین کنید.
با خواندن این پست، یاد گرفتید:
- متمرکز سازی logic رسیدگی به خطا با استفاده از Redux workflow
- نحوه ترکیب reducer ها در یک object واحد
- واکشی داده ها برای details view و نحوه ایجاد آن view
بابت خواندن این مقاله از شما تشکر میکنیم و امیدواریم که برای شما مفید واقع قرار گرفته باشد.
در قسمت بعدی این سری آموزشی، قصد داریم نحوه استفاده از input های داینامیک برای ایجاد form ها را یاد بگیریم. علاوه بر این، قصد داریم کامپوننتهای modal را معرفی کنیم و یک component برای POST action ایجاد کنیم.