14 نصائح لكتابة رمز التفاعل النظيف. الجزء 2

اليوم ننشر الجزء الثاني من المادة حول كتابة التعليمات البرمجية النظيفة عند تطوير تطبيقات React. إليك بعض النصائح المفيدة.



اقرأ الجزء الأول

8. تحويل العناصر المكررة إلى مكونات


يمكن أن يسمى تحويل العناصر المكررة إلى مكونات مناسبة لإعادة الاستخدام باسم "مكونات" هذه العناصر.

كل مطور له أسبابه الخاصة وراء كتابته لتكرار كود التفاعل. قد يكون هذا عملاً متعمدًا ، أو قد يكون حادثًا.

بغض النظر عن سبب ظهور أجزاء التعليمات البرمجية نفسها في التطبيق ، يجب على المبرمج التفكير في كيفية تحسين الموقف.

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

ألقِ نظرة على المثال التالي وفكر في كيفية تحسينه:

const SomeComponent = () => (   <Body noBottom>     <Header center>Title</Header>     <Divider />     <Background grey>       <Section height={500}>         <Grid spacing={16} container>           <Grid xs={12} sm={6} item>             <div className={classes.groupsHeader}>               <Header center>Groups</Header>             </div>           </Grid>           <Grid xs={12} sm={6} item>             <div>               <img src={photos.groups} alt="" className={classes.img} />             </div>           </Grid>         </Grid>       </Section>     </Background>     <div>       <Section height={500}>         <Grid spacing={16} container>           <Grid xs={12} sm={6} item>             <div className={classes.labsHeader}>               <Header center>Labs</Header>             </div>           </Grid>           <Grid xs={12} sm={6} item>             <div>               <img src={photos.labs} alt="" className={classes.img} />             </div>           </Grid>         </Grid>       </Section>     </div>   </Body> ) 

إذا كنت بحاجة الآن إلى تغيير معلمات الشبكة من xs={12} sm={6} إلى xs={12} sm={4} ، فلن تكون هذه المهمة ممتعة بشكل خاص. الحقيقة هي أنه لهذا عليك تعديل الكود في أربعة أماكن.

إن جمال النهج المكون هو أنه يسمح لك بحل المشكلات المشابهة لما سبق ، وتغيير الكود في مكان واحد فقط. في حالتنا ، سينعكس هذا التغيير في كل مكان تستخدم فيه الشبكات:

 const SomeComponent = ({ classes, xs = 12, sm = 6, md, lg }) => {  const BodySection = ({ header, src }) => {    const gridSizes = { xs, sm, md, lg }    return (      <Section height={500}>        <Grid spacing={16} container>          <Grid {...gridSizes} item>            <div className={classes.groupsHeader}>              <Header center>{header}</Header>            </div>          </Grid>          <Grid {...gridSizes} item>            <div>              <img src={src} alt="" className={classes.img} />            </div>          </Grid>        </Grid>      </Section>    )  }  return (    <Body noBottom>      <Header center>Title</Header>      <Divider />      <Background grey>        <BodySection header="Groups" src={photos.groups} />      </Background>      <div>        <BodySection header="Labs" src={photos.labs} />      </div>    </Body>  ) } 

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

9. نسعى جاهدين للحفاظ على مكوناتك بسيطة قدر الإمكان.


عند العمل على تطبيقات المبيعات ، صادفت أحيانًا عدم الحاجة إلى السعي لبساطة المكون ، ولكن الحاجة إلى تجنب المواقف التي تصبح فيها المكونات معقدة للغاية.

فيما يلي مثال لمكون معقد بشكل غير ضروري. ويمثلها ConfirmAvailability.js :

 import React from 'react' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' import Time from 'util/time' /** *     .    ,     , ,  ,  * ,    .        .     -     . * * :     Date().getTimezoneOffset().     : *   1.      . *   2.     UTC -     ,   UTC   .  ,       *     UTC. */ export default class TimeZonePicker extends React.Component {  state = {    time: new Date(),    offset: -(new Date().getTimezoneOffset() / 60),  }  componentDidMount() {    this.props.setOffset(this.state.offset)  }  handleChange = (event) => {    const d = new Date()    d.setTime(      d.getTime() +        d.getTimezoneOffset() * 60 * 1000 +        event.target.value * 3600 * 1000,    )    this.setState({      time: d,      offset: event.target.value,    })    this.props.setOffset(event.target.value)  }  render() {    const timezones = []    for (let i = -12; i <= 14; i++) {      timezones.push(        <MenuItem key={i} value={i}>          {i > 0 ? '+' : null}          {i}        </MenuItem>,      )    }    return (      <React.Fragment>        <Grid container justify="space-between">          <div>            <Typography>Current time</Typography>            <Typography variant="h6" gutterBottom>              {Time.formatTime(this.state.time)}            </Typography>          </div>          <div>            <Typography>Set timezone</Typography>            <Select value={this.state.offset} onChange={this.handleChange}>              {timezones}            </Select>          </div>        </Grid>      </React.Fragment>    )  } } 

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

في السابق ، تم تخزين جميع الشفرات في ملف واحد. الآن نحن تقسيمها إلى ملفين. فيما يلي محتويات الملف الأول - SelectTimeZone.js :

 import React from 'react' /** *     .    ,     , ,  ,  * ,    .        .     -     . * * :     Date().getTimezoneOffset().     : *   1.      . *   2.     UTC -     ,   UTC   .  ,       *     UTC. */ class SelectTimeZone extends React.Component {  state = {    time: new Date(),    offset: -(new Date().getTimezoneOffset() / 60),  }  componentDidMount() {    this.props.setOffset(this.state.offset)  }  handleChange = (event) => {    const d = new Date()    d.setTime(      d.getTime() +        d.getTimezoneOffset() * 60 * 1000 +        event.target.value * 3600 * 1000,    )    this.setState({      time: d,      offset: event.target.value,    })    this.props.setOffset(event.target.value)  }  getTimeZones = () => {    const timezones = []    for (let i = -12; i <= 14; i++) {      timezones.push(        <MenuItem key={i} value={i}>          {i > 0 ? '+' : null}          {i}        </MenuItem>,      )    }    return timezones  }  render() {    return this.props.render({      ...this.state,      getTimeZones: this.getTimeZones,    })  } } 

إليك ما يبدو عليه الملف الثاني - TimeZonePicker.js :

 import React from 'react' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' import Time from 'util/time' const TimeZonePicker = () => (  <SelectTimeZone    render={({ time, offset, getTimeZones, handleChange }) => (      <Grid container justify="space-between">        <div>          <Typography>Current time</Typography>          <Typography variant="h6" gutterBottom>            {Time.formatTime(time)}          </Typography>        </div>        <div>          <Typography>Set timezone</Typography>          <Select value={offset} onChange={handleChange}>            {getTimeZones()}          </Select>        </div>      </Grid>    )}  /> ) export default TimeZonePicker 

بعد المعالجة ، تبين أن رمز المشروع أكثر نظافة من ذي قبل. لقد استخرجنا المنطق من جزء العرض التقديمي للمكون. الآن ، بالإضافة إلى ذلك ، سيتم تبسيط اختبار وحدة المشروع إلى حد كبير.

10. استخدام useReducer عند تعقيد useState


كلما زادت شظايا الحالة التي يجب عليك معالجتها في مشروع ، كلما زاد استخدام استخدام useState .

هذا ، على سبيل المثال ، قد يبدو كالتالي:

 import React from 'react' import axios from 'axios' const useFrogs = () => {  const [fetching, setFetching] = React.useState(false)  const [fetched, setFetched] = React.useState(false)  const [fetchError, setFetchError] = React.useState(null)  const [timedOut, setTimedOut] = React.useState(false)  const [frogs, setFrogs] = React.useState(null)  const [params, setParams] = React.useState({ limit: 50 })  const timedOutRef = React.useRef()  function updateParams(newParams) {    if (newParams != undefined) {      setParams(newParams)    } else {      console.warn(        'You tried to update state.params but the parameters were null or undefined',      )    }  }  function formatFrogs(newFrogs) {    const formattedFrogs = newFrogs.reduce((acc, frog) => {      const { name, age, size, children } = frog      if (!(name in acc)) {        acc[name] = {          age,          size,          children: children.map((child) => ({            name: child.name,            age: child.age,            size: child.size,          })),        }      }      return acc    }, {})    return formattedFrogs  }  function addFrog(name, frog) {    const nextFrogs = {      ...frogs,      [name]: frog,    }    setFrogs(nextFrogs)  }  function removeFrog(name) {    const nextFrogs = { ...frogs }    if (name in nextFrogs) delete nextFrogs[name]    setFrogs(nextFrogs)  }  React.useEffect(() => {    if (frogs === null) {      if (timedOutRef.current) clearTimeout(timedOutRef.current)      setFetching(true)      timedOutRef.current = setTimeout(() => {        setTimedOut(true)      }, 20000)      axios        .get('https://somefrogsaspi.com/api/v1/frogs_list/', { params })        .then((response) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          setFetching(false)          setFetched(true)          if (timedOut) setTimedOut(false)          if (fetchError) setFetchError(null)          setFrogs(formatFrogs(response.data))        })        .catch((error) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          console.error(error)          setFetching(false)          if (timedOut) setTimedOut(false)          setFetchError(error)        })    }  }, [])  return {    fetching,    fetched,    fetchError,    timedOut,    frogs,    params,    addFrog,    removeFrog,  } } export default useFrogs 

ستصبح أكثر ملاءمة للعمل مع كل هذا إذا قمت بترجمة هذا الرمز لاستخدام useReducer :

 import React from 'react' import axios from 'axios' const initialFetchState = {  fetching: false  fetched: false  fetchError: null  timedOut: false } const initialState = {  ...initialFetchState,  frogs: null  params: { limit: 50 } } const reducer = (state, action) => {  switch (action.type) {    case 'fetching':      return { ...state, ...initialFetchState, fetching: true }    case 'fetched':      return { ...state, ...initialFetchState, fetched: true, frogs: action.frogs }    case 'fetch-error':      return { ...state, ...initialFetchState, fetchError: action.error }    case 'set-timed-out':      return { ...state, ...initialFetchState, timedOut: true }    case 'set-frogs':      return { ...state, ...initialFetchState, fetched: true, frogs: action.frogs }    case 'add-frog':      return { ...state, frogs: { ...state.frogs, [action.name]: action.frog }}    case 'remove-frog': {      const nextFrogs = { ...state.frogs }      if (action.name in nextFrogs) delete nextFrogs[action.name]      return { ...state, frogs: nextFrogs }    }    case 'set-params':      return { ...state, params: { ...state.params, ...action.params } }      default:        return state  } } const useFrogs = () => {  const [state, dispatch] = React.useReducer(reducer, initialState)  const timedOutRef = React.useRef()  function updateParams(params) {    if (newParams != undefined) {      dispatch({ type: 'set-params', params })    } else {      console.warn(        'You tried to update state.params but the parameters were null or undefined',      )    }  }  function formatFrogs(newFrogs) {    const formattedFrogs = newFrogs.reduce((acc, frog) => {      const { name, age, size, children } = frog      if (!(name in acc)) {        acc[name] = {          age,          size,          children: children.map((child) => ({            name: child.name,            age: child.age,            size: child.size,          })),        }      }      return acc    }, {})    return formattedFrogs  }  function addFrog(name, frog) {    dispatch({ type: 'add-frog', name, frog })  }  function removeFrog(name) {    dispatch({ type: 'remove-frog', name })  }  React.useEffect(() => {    if (frogs === null) {      if (timedOutRef.current) clearTimeout(timedOutRef.current)      timedOutRef.current = setTimeout(() => {        setTimedOut(true)      }, 20000)      axios        .get('https://somefrogsaspi.com/api/v1/frogs_list/', { params })        .then((response) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          const frogs = formatFrogs(response.data)          dispatch({ type: 'set-frogs', frogs })        })        .catch((error) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          console.error(error)          dispatch({ type: 'fetch-error', error })        })    }  }, [])  return {    fetching,    fetched,    fetchError,    timedOut,    frogs,    params,    addFrog,    removeFrog,  } } export default useFrogs 

على الرغم من أن هذا النهج ربما لا يكون أكثر نظافة من استخدام useState ، كما ترون من خلال النظر إلى الكود ، إلا أنه من السهل الحفاظ على الكود الجديد. هذا يرجع إلى حقيقة أنه عند استخدام useReducer لا يتعين على المبرمج القلق بشأن تحديثات الحالة في أجزاء مختلفة من الخطاف ، حيث يتم تحديد كل هذه العمليات في مكان واحد داخل reducer .

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

11. استخدام إعلانات الوظائف في المواقف المثيرة للجدل


من الأمثلة الجيدة لاستخدام هذه التوصية إنشاء useEffect تنظيف useEffect :

 React.useEffect(() => {  setMounted(true)  return () => {    setMounted(false)  } }, []) 

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

 React.useEffect(() => {  setMounted(true)  return function cleanup() {    setMounted(false)  } }, []) 

يتيح لنا هذا النهج أن نوضح بوضوح الدور الذي تلعبه الوظيفة المرتجعة.

12. استخدام أجمل


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

13. نسعى جاهدين لاستخدام اختزال الإعلانات


يمكن التعبير عن جوهر هذه التوصية في المثالين التاليين.

إليك نسخة مختصرة من إعلان الجزء:

 const App = () => (  <>    <FrogsTable />    <FrogsGallery />  </> ) 

هنا هو النسخة الكاملة:

 const App = () => (  <React.Fragment>    <FrogsTable />    <FrogsGallery />  </React.Fragment> ) 

14. اتبع ترتيبًا معينًا لوضع العناصر عند كتابة التعليمات البرمجية.


عندما أكتب رمزًا ، أفضل ترتيب بعض الأوامر بترتيب معين. على سبيل المثال ، أفعل ذلك عند استيراد الملفات (الاستثناء هنا هو استيراد react فقط):

 import React from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' import FrogsGallery from './FrogsGallery' import FrogsTable from './FrogsTable' import Stations from './Stations' import * as errorHelpers from '../utils/errorHelpers' import * as utils from '../utils/' 

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

أنا ، بالسعي من أجل نقاء رمز مشاريعي ، استخدم القواعد التالية التي تنطبق على الترتيب الذي تتبع به:

  1. استيراد رد فعل.
  2. استيراد مكتبات (حسب الترتيب الأبجدي).
  3. الأوامر المطلقة لاستيراد كيانات من مشروع (حسب الترتيب الأبجدي).
  4. أوامر الاستيراد النسبية (بالترتيب الأبجدي).
  5. import * as أوامر النموذج import * as .
  6. import './<some file>.<some ext>' أوامر النموذج import './<some file>.<some ext>' .

وإليك الطريقة التي أفضل تنظيمها ، على سبيل المثال ، المتغيرات. قل - خصائص الكائنات:

 const character = (function() {  return {    cry() {      //    },    eat() {      //    },    hop() {      //    },    jump() {      //    },    punch() {      //    },    run() {      //    },    scratch() {      //    },    scream() {      //    },    sleep() {      //    },    walk() {      //    },    yawn() {      //    },  } })() 

إذا اتبعت قواعد معينة لترتيب الكيانات عند كتابة التعليمات البرمجية - فسيكون لهذا تأثير مفيد على نقاوتها.

النتائج


لقد قدمنا ​​لك نصائح لكتابة رمز تطبيق React النظيف. نأمل أن تجد شيئًا ما مفيدًا لك.

أعزائي القراء! ما هي التوصيات التي تضيفها إلى النصائح المقدمة في هذه المقالة؟

Source: https://habr.com/ru/post/ar465813/


All Articles