Sbírka užitečných tipů a triků pro začátečníky pro React

Toto je část mé série „React pro začátečníky“ o představení Reactu, jeho základních funkcích a osvědčených postupech, které je třeba dodržovat. Přibývají další články!< Jak je patrné z názvu tohoto článku, je zaměřen na začátečníky.

Vlastně jsem se React začal učit před několika měsíci. Hodně mi pomohlo čtení dokumentace Reactu, open source projektů a článků Medium.

Bezpochyby nejsem odborník na React. A tak jsem o tomto tématu hodně četl. Také budování malých projektů mi pomohlo lépe poznat React. Po cestě jsem přijal některé osvědčené postupy – a chci se s vámi podělit zde. Takže začneme.

Pojmenujte své komponenty

Chcete-li zjistit, která komponenta obsahuje chybu, je důležité vždy komponentu pojmenovat.

Ještě více, když začnete používat React Router nebo knihovny třetích stran.

// Avoid thoses notations 
export default () => {};
export default class extends React.Component {};

Probíhá debata o tom, zda použít výchozí nebo pojmenovaný import. Všimněte si, že výchozí import nezajišťuje, že název komponenty je v projektu konzistentní. Kromě toho bude třást stromy méně efektivní.

Bez ohledu na to, jak komponentu vystavíte, pojmenujte ji

Musíte definovat název třídy nebo název proměnné (pro funkční komponenty), která je hostitelem komponenty.

React z něj ve skutečnosti odvodí název komponenty v chybových zprávách.

export const Component = () => <h1>I'm a component</h1>;
export default Component;

// Define user custom component name
Component.displayName = 'My Component';

Zde je moje poslední rada ohledně importů (převzatá odtud):Pokud používáte ESLint, měli byste zvážit nastavení následujících dvou pravidel:

"rules": {
    // Check named import exists
    "import/named": 2, 
  
    // Set to "on" in airbnb preset
    "import/prefer-default-export": "off"
}

Upřednostňovat funkční komponenty

Pokud máte mnoho komponent, jejichž účelem je pouze zobrazovat data, využijte mnoho způsobů, jak definovat komponentu React:

class Watch extends React.Component {
  render () {
    return <div>{this.props.hours}:{this.props.minutes}</div>
  }
}

// Equivalent functional component
const Watch = (props) =>
  <div>{props.hours}:{props.minutes}</div>;

Oba úryvky definují stejné Watch komponent. Druhý je však mnohem kratší a dokonce klesá o this pro přístup k rekvizitám v šabloně JSX.

Nahradit prvky div fragmenty

Každá komponenta musí vystavit jedinečný kořenový prvek jako šablonu. Aby bylo dodrženo toto pravidlo, běžným řešením je zabalit šablonu do div .

React 16 nám přináší novou funkci nazvanou Fragmenty . Nyní můžete nahradit ty zbytečné div s React.Fragment s.

Výstupní šablonou bude obsah fragmentu bez obalu.

const Login = () => 
  <div><input name="login"/><input name="password"/></div>;

const Login = () =>
  <React.Fragment><input name="login"/><input name="password"/></React.Fragment>;

const Login = () => // Short-hand syntax
  <><input name="login"/><input name="password"/></>;

Při nastavování stavu buďte opatrní

Jakmile je vaše aplikace React dynamická, musíte se vypořádat se stavy komponent.

Použití stavů se zdá docela jednoduché. Inicializujte obsah stavu v constructor a poté zavolejte setState aktualizovat stav.

Z nějakého důvodu možná budete muset použít aktuální stav nebo rekvizity hodnoty při volání setState pro nastavení hodnoty dalšího stavu.

// Very bad pratice: do not use this.state and this.props in setState !
this.setState({ answered: !this.state.answered, answer });

// With quite big states: the tempatation becomes bigger 
// Here keep the current state and add answer property
this.setState({ ...this.state, answer });

Problém je v tom, že React nezaručuje this.state a this.props mít hodnotu, kterou očekáváte. setState je asynchronní, protože aktualizace stavu jsou dávkové pro optimalizaci manipulací DOM (viz podrobnosti v dokumentech React a tomto problému).

// Note the () notation around the object which makes the JS engine
// evaluate as an expression and not as the arrow function block
this.setState((prevState, props) 
              => ({ ...prevState, answer }));

Chcete-li zabránit poškozeným stavům, musíte použít setState s parametrem funkce. Poskytuje správné hodnoty stavu a rekvizit.

Vazba funkcí komponent

Existuje mnoho způsobů, jak svázat události prvku s jeho komponentou, a některé se nedoporučují.

První a legitimní řešení se objevuje v dokumentaci React:

class DatePicker extends React.Component {
   handleDateSelected({target}){
     // Do stuff
   }
   render() {   
     return <input type="date" onChange={this.handleDateSelected}/>
   }
 }

Může vás to zklamat, když zjistíte, že to nefunguje.

Důvodem je, že při použití JSX this hodnota není vázána na instanci komponenty. Zde jsou tři alternativy, jak to udělat:

// #1: use an arrow function
<input type="date" onChange={(event) => this.handleDateSelected(event)}/>

// OR #2: bind this to the function in component constructor
constructor () { 
  this.handleDateSelected = this.handleDateSelected.bind(this); 
}

// OR #3: declare the function as a class field (arrow function syntax)
handleDateSelected = ({target}) => {
   // Do stuff
}

Použití funkce šipky v JSX jako v prvním příkladu se na první pohled zdá lákavé. Ale nedělejte to. Ve skutečnosti bude vaše funkce šipky vytvořena znovu při vykreslování každé komponenty a bude to zhoršovat výkon.

Pozor také na poslední řešení. Používá syntaxi polí tříd, která je pouze návrhem pro ECMAScript.

To znamená, že k transpilaci kódu musíte použít Babel. Pokud syntaxe není nakonec přijata, váš kód se rozpadne.

Přijmout vzor kontejneru (i s Redux)

V neposlední řadě designový vzor kontejneru. To vám umožní sledovat princip oddělení obav v komponentě React.

export class DatePicker extends React.Component {
  state = { currentDate: null };

  handleDateSelected = ({target}) =>
     this.setState({ currentDate: target.value });

  render = () => 
     <input type="date" onChange={this.handleDateSelected}/>
}

Jedna komponenta zpracovává vykreslování šablony a uživatelské akce na stejném místě. Použijme místo toho dvě komponenty:

const DatePicker = (props) => 
  <input type="date" onChange={props.handleDateSelected}/>
        
export class DatePickerController extends React.Component { 
  // ... No changes except render function ...
  render = () => 
     <DatePicker handleDateSelected={this.handleDateSelected}/>;
}

Tady je trik. DatePickerContainer v případě potřeby zpracovává uživatelské interakce a volání API. Poté vykreslí DatePicker a zásoby rekvizit.

Díky tomuto vzoru kontejnerová komponenta nahrazuje komponentu prezentační. Tato funkční součást se bez rekvizit stává nepoužitelnou.

export const DatePickerContainer = 
 connect(mapStateToProps, mapDispatchToProps)(DatePickerController);

Kromě toho, pokud používáte Redux jako správce stavu pro vaši aplikaci, je také dobře kompatibilní s tímto vzorem.

connect funkce vstřikuje podpěry do součásti. V našem případě bude napájet ovladač, který tyto rekvizity předá komponentě.

Obě komponenty tak budou mít přístup k datům Redux. Zde je úplný kód pro návrhový vzor kontejneru (bez syntaxe Redux nebo tříd).

Bonus:Opravte vrtání podpěr

Při psaní mého výukového projektu pro React jsem si všiml špatného vzorce, který mi vadil s rekvizitami. Na každé stránce jsem měl hlavní komponentu, která používala obchod a vykreslovala nějaké vnořené hloupé komponenty.

Jak mohou hluboce vnořené hloupé komponenty přistupovat k datům hlavní komponenty? Ve skutečnosti nemohou – ale můžete to opravit:

  • zabalení hloupé komponenty do kontejneru (stane se inteligentní)
  • nebo předejte rekvizity z horní komponenty

Druhé řešení znamená, že komponenty mezi horní komponentou a němou komponentou budou muset předat rekvizity, které nepotřebují.

const Page = props => <UserDetails fullName="John Doe"/>;
   
const UserDetails = props => 
<section>
    <h1>User details</h1>
    <CustomInput value={props.fullName}/> // <= No need fullName but pass it down
</section>;

const inputStyle = {
   height: '30px',
   width: '200px',
	fontSize: '19px',
   border: 'none',
   borderBottom: '1px solid black'
};
const CustomInput = props => // v Finally use fullName value from Page component
   <input style={inputStyle} type="text" defaultValue={props.value}/>;

Komunita React tento problém pojmenovala vrtání vrtule .

Page je hlavní komponenta, která načítá detaily uživatele. Tyto údaje je nutné předat přes UserDetails a přeneste jej na CustomInput .

V tomto příkladu podpěra prochází pouze jednou komponentou, která ji nepotřebuje. Ale může to být mnohem více, pokud máte opakovaně použitelné komponenty. Například kódová základna Facebooku obsahuje několik tisíc opakovaně použitelných komponent!

Nebojte se, naučím vás tři způsoby, jak to opravit. První dvě metody se objevují v dokumentaci kontextového rozhraní API:prop pro děti a render prop .

// #1: Use children prop
const UserDetailsWithChildren = props => 
<section>
    <h1>User details (with children)</h1>
    {props.children /* <= use children */} 
</section>;

// #2: Render prop pattern
const UserDetailsWithRenderProp = props => 
<section>
    <h1>User details (with render prop)</h1>
    {props.renderFullName() /* <= use passed render function */}
</section>;

const Page = () => 
<React.Fragment>
    {/* #1: Children prop */}
    <UserDetailsWithChildren>
        <CustomInput value="John Doe"/> {/* Defines props.children */}
    </UserDetailsWithChildren>
  
    {/* #2: Render prop pattern */}
    {/* Remember: passing arrow functions is a bad pratice, make it a method of Page class instead */}
    <UserDetailsWithRenderProp renderFullName={() => <CustomInput value="John Doe"/>}/>
</React.Fragment>;

Tato řešení jsou si dost podobná. Preferuji použití dětí, protože to funguje dobře v rámci metody renderování. Všimněte si, že tyto vzory můžete také rozšířit poskytnutím hlouběji vnořených komponent.

const Page = () =>  
<PageContent>
  <RightSection> 
    <BoxContent>
      <UserDetailsWithChildren>
          <CustomInput value="John Doe"/>
      </UserDetailsWithChildren>
    </BoxContent>
  </RightSection>
</PageContent>

Třetí příklad používá experimentální kontextové API.

const UserFullNameContext = React.createContext('userFullName');

const Page = () => 
<UserFullNameContext.Provider value="John Doe"> {/* Fill context with value */}
    <UserDetailsWithContext/>
</UserFullNameContext.Provider>;

const UserDetailsWithContext = () => // No props to provide
<section>
    <h1>User details (with context)</h1>
    <UserFullNameContext.Consumer> {/* Get context value */}
        { fullName => <CustomInput value={fullName}/> }
    </UserFullNameContext.Consumer>
</section>;

Tuto metodu nedoporučuji, protože využívá experimentální funkci. (A to je důvod, proč se API nedávno změnilo na vedlejší verzi.) Také vás to nutí vytvořit globální proměnnou pro uložení kontextu a vaše komponenta získá nejasnou novou závislost (kontext může obsahovat cokoli).

To je ono!

Děkuji za přečtení. Doufám, že jste se dozvěděli nějaké zajímavé tipy o React!

Pokud byl tento článek pro vás užitečný, klikněte na ? tlačítko několikrát, aby ostatní našli článek a projevili svou podporu! ?

Nezapomeňte mě sledovat, abyste byli informováni o mých nadcházejících článcích ?

Toto je část mé série „React pro začátečníky“ o představení Reactu, jeho základních funkcích a osvědčených postupech, které je třeba dodržovat. <Podívejte se na mé další články

➥ JavaScript

  • Jak zlepšit své dovednosti JavaScript psaním vlastního rámce pro vývoj webu?
  • Běžné chyby, kterým je třeba se vyhnout při práci s Vue.js

➥ Tipy a triky

  • Zastavte bolestivé ladění JavaScriptu a využijte Intellij pomocí mapy zdroje
  • Jak bez námahy snížit obrovské množství balíků JavaScript