Վիճակ և կյանքի ցիկլ

Այս էջը ներկայացնում է վիճակի(state) և կյանքի ցիկլի(lifecycle) գաղափարը React կոմպոնենտում։ Դուք կարող եք գտնել կոմպոնենտի մանրամասն API հղումն այստեղ։

Դիտարկենք «ժամացույցի» աշխատանքի օրինակը` նկարագրված վերջին գլուխներից մեկում։ Էլեմենտների արտապատկերում գլխում մենք UI-ը թարմացնելու միայն մեկ եղանակ ենք սովորել։ Մենք կանչում ենք ReactDOM.render()-ը` փոփոխելու արտապատկերված ելքային արժեքը.

function tick() {
  const element = (
    <div>
      <h1>Ողջույն, աշխարհ</h1>
      <h2>Ժամը {new Date().toLocaleTimeString()}-ն է։</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Փորձել CodePen-ում

Այս գլխում մենք կսովորենք, թե ինչպես դարձնել Clock կոմպոնենտը իսկապես բազմակի օգտագործման ենթակա և ինկապսուլացված։ Այն կտեղադրի իր սեփական Ժամաչափը(timer) և կթարմացնի ինքն իրեն ամեն վայրկյան։

Սկզբում առանձնացնենք ժամանակը ցույց տվող կոմպոնենտը.

function Clock(props) {
  return (
    <div>
      <h1>Ողջույն, աշխարհ</h1>
      <h2>Ժամը {props.date.toLocaleTimeString()}-ն է։</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Փորձել CodePen-ում

Այնուամենայնիվ, բաց է թողնված առանցքային պահանջ. այն փաստը, որ Clock-ը տեղադրում է ժամաչափ և թարմացնում է UI-ը ամեն վայրկյան, պետք է լինի Clock-ի իրականացման տարր։

Լավագույն դեպքում մենք ցանկանում ենք գրել սա մեկ անգամ և ունենալ ինքնաթարմացվող Clock.

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Սա իրականացնելու համար մենք կարիք ունենք ավելացնելու state Clock կոմպոնենտին։

State-ը նման է props-ին, բայց այն private է և ամբողջությամբ կառավարվում է կոմպոնենտի կողմից։

Ֆունկցիայի փոխարկումը կլասի

Դուք կարող եք փոխարկել Clock-ի նման ֆունկցիա-կոմպոնենտը կլասի հինգ քայլով.

  1. Ստեղծել նույն անունով ES6-կլասeng, որն ընդլայնվում է React.Component-ից։
  2. Նրանում ավելացնել մեկ դատարկ մեթոդ` render() անունով։
  3. Տեղափոխել ֆունկցիայի մարմինը render() մեթոդի մեջ։
  4. Փոխարինել propsthis.props-ով render()-ի մարմնում։
  5. Ջնջել դատարկ մնացած ֆունկցիան։
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Ողջույն, աշխարհ</h1>
        <h2>Ժամը {this.props.date.toLocaleTimeString()}-ն է։</h2>
      </div>
    );
  }
}

Փորձել CodePen-ում

Clock-ը այժմ հայտարարված է որպես կլաս, ոչ թե ֆունկցիա։

render մեթոդը կկանչվի ամեն անգամ, երբ թարմացում տեղի ունենա, բայց այնքան ժամանակ մինչդեռ մենք արտապատկերում ենք <Clock />-ը նույն DOM հանգույցի մեջ, Clock կլասի միայն մեկ օրինակ(instance) կօգտագործվի։ Այն թույլ կտա մեզ օգտագործել մի քանի լրացուցիչ հատկություններ, ինչպիսիք են լոկալ state-ը և կյանքի ցիկլի մեթոդները։

Ավելացնել լոկալ state կլասին

Մենք կտեղափոխենք date-ը props-ից state երեք քայլով։

  1. Փոխարինել this.props.datethis.state.date-ով render() մեթոդում.
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Ողջույն, աշխարհ</h1>
        <h2>Ժամը {this.state.date.toLocaleTimeString()}-ն է։</h2>
      </div>
    );
  }
}
  1. Ավելացնել կլասի կոնստրուկտորeng, որը կվերագրի նախնական this.state-ը։
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Ողջույն, աշխարհ</h1>
        <h2>Ժամը {this.state.date.toLocaleTimeString()}-ն է։</h2>
      </div>
    );
  }
}

Ուշադրություն դարձրեք, թե ինչպես ենք փոխանցում props-ը հիմնական կոնստրուկտորին.

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

Կլաս-կոմպոնենտները պետք է միշտ կանչեն հիմնական կոնստրուկտորը props-ով։

  1. Ջնջել date prop-ը <Clock /> էլեմենտից.
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Փոքր-ինչ ավելի ուշ մենք Ժամաչափի կոդը կվերադարձնենք և կտեղադրենք կոմպոնենտի մեջ։

Արդյունքը նման կլինի սրան.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Ողջույն, աշխարհ</h1>
        <h2>Ժամը {this.state.date.toLocaleTimeString()}-ն է։</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Փորձել CodePen-ում

Հաջորդիվ, Clock-ում մենք կտեղադրենք իր սեփական ժամաչափը, որը կթարմացնի ինքն իրեն ամեն վայրկյան։

Կյանքի ցիկլի մեթոդների ավելացում կլասում

Շատ կոմպոնենտներ ունեցող հավելվածներում, կոմպոնենտների ոչնչացվելուց հետո, շատ կարևոր է նրանց կողմից վերցված ռեսուրսների ազատումը։

Մենք ցանկանում ենք տեղադրել ժամաչափeng, երբ Clock-ը արտապատկերվել է DOM-ում առաջին անգամ։ Սա React-ում կոչվում է «mounting»։

Մենք նաև ցանկանում ենք ջնջել ժամաչափըeng հենց որ Clock-ի կողմից ստեղծված DOM հանգույցը ջնջվի։ Սա React-ում կոչվում է «unmounting»։

Մենք կարող ենք հայտարարել հատուկ մեթոդներ կլաս-կոմպոնենտում, որպեսզի աշխատեցնենք կոդ կոմպոնենտի «mount»-ի և «unmount»-ի ժամանակ.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Ողջույն, աշխարհ</h1>
        <h2>Ժամը {this.state.date.toLocaleTimeString()}-ն է։</h2>
      </div>
    );
  }
}

Այս մեթոդները կոչվում են «կյանքի ցիկլի մեթոդներ»։

componentDidMount() մեթոդը կանչվում է կոմպոնենտի ելքային արժեքի` DOM-ում արտապատկերվելուց հետո։ Սա հարմար տեղ է ժամաչափ տեղադրելու համար.

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

Ուշադրություն դարձրեք, թե ինչպես ենք մենք պահում ժամաչափի ID-ն this-ում։

Քանի դեռ this.props-ը տեղադրված է React-ի կողմից և this.state-ը ունի հատուկ նշանակություն, դուք ազատ եք ավելացնելու հավելյալ դաշտեր կլասում ինքնուրույն, երբ կարիք ունեք պահելու ինչ-որ բան, որը կապ չունի տվյալների հոսքի հետ (օրինակ` ժամաչափի ID-ի նման)։

Մենք կջնջենք ժամաչափը componentWillUnmount() կյանքի ցիկլի մեթոդում.

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

Վերջապես, մենք կիրականացնենք tick() անունով մեթոդ, որին Clock կոմպոնենտը կաշխատեցնի ամեն վայրկյան։

Այն կօգտագործի this.setState()-ը` պլանավորելու կոմպոնենտի լոկալ state-ի թարմացումները.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Ողջույն, աշխարհ</h1>
        <h2>Ժամը {this.state.date.toLocaleTimeString()}-ն է։</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Փորձել CodePen-ում

Այժմ ժամացույցը թարմանում է ամեն վայրկյան։

Եկեք արագ իմի բերենք այն, թե ինչ է տեղի ունենում և ինչ հերթականությամբ են կանչվում մեթոդները.

  1. Երբ <Clock />-ը փոխանցվում է ReactDOM.render()-ին, React-ը կանչում է Clock կոմպոնենտի կոնստրուկտորը։ Քանի որ Clock-ը կարիք ունի պատկերելու ընթացիկ ժամը, այն սկզբնարժեքավորում է this.state-ը օբյեկտով, որը պարունակում է ընթացիկ ժամը։ Ավելի ուշ մենք կթարմացնենք այս state-ը։
  2. Հետո React-ը կանչում է Clock կոմպոնենտի render() մեթոդը։ Ահա թե ինչպես է React-ն իմանում, թե ինչ պիտի պատկերի էկրանին։ Այնուհետև React-ը թարմացնում է DOM-ը , որպեսզի ստանա Clockrender-ի ելքային արժեքը։
  3. Երբ Clock-ի ելքային արժեքը ավելացված է DOM-ում, React-ը կանչում է componentDidMount() կյանքի ցիկլի մեթոդը։ Դրա ներսում Clock կոմպոնենտը հարցնում է զննարկչին ժամաչափ տեղադրելու համար, որպեսզի կանչի կամպոնենտի tick() մեթոդը վայրկյանը մեկ։
  4. Վայրկյանը մեկ զննարկիչը կանչում է tick() մեթոդը։ Դրա ներսում, Clock կոմպոնենտը պլանավորում է UI-ի թարմացումը` կանչելով setState() ընթացիկ ժամը պարունակող օբյեկտով։ setState()-ի կանչի շնորհիվ React-ը գիտի, որ state-ը փոխվել է և կանչում է render() մեթոդը կրկին` իմանալու համար, թե ինչը պետք է լինի էկրանին։ Այս անգամ, render()-ում this.state.date-ը կլինի ուրիշ, և այսպիսով, render-ի ելքային արժեքը կներառի թարմացված ժամանակը։ React-ը կթարմացնի DOM-ը համապատասխանաբար։
  5. Եթե Clock կոմպոնենտը երբևիցե ջնջվի DOM-ից, React-ը կկանչի componentWillUnmount() կյանքի ցիկլի մեթոդը, որի հետևանքով ժամաչափը կկանգնի։

State-ի ճիշտ օգտագործում

Կա երեք բան setState()-ի մասին, որ պետք է իմանալ։

Մի փոփոխեք state-ը անմիջականորեն

Օրինակ, սա չի վերա-արտապատկերի կոմպոնենտը.

// Սխալ
this.state.comment = 'Ողջույն';

Փոխարենը` օգտագործեք setState().

// Ճիշտ
this.setState({comment: 'Ողջույն'});

this.state-ին վերագրում կարող եք կատարել միայն կոնստրուկտորում։

State-ը կարող է թարմանալ ասինխրոն

React-ը կարող է խմբավորել մի քանի setState() կանչեր մեկ թարմացման մեջ արտադրողականության համար։

Քանի որ this.props-ը և this.state-ը կարող են թարմացված լինել ասինխրոն, դուք չպետք է հիմնվեք նրանց արժեքների վրա` հաջորդ state-ը հաշվելու համար։

Օրինակ, այս կոդը կարող է ճիշտ չթարմացնել counter-ը.

// Սխալ
this.setState({
  counter: this.state.counter + this.props.increment,
});

Սա ֆիքսելու համար օգտագործեք setState()-ի երկրորդ ձևը, որն ընդունում է ֆունկցիա օբյեկտի փոխարեն։ Այս ֆունկցիան կստանա նախորդ state-ը որպես առաջին արգումենտ և թարմացման պահի props-ը, որպես երկրորդ արգումենտ.

// Ճիշտ
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

Վերը մենք օգտագործում ենք սլաք-ֆունկցիաeng, բայց այն աշխատում է նաև սովորական ֆունկցիաների հետ.

// Ճիշտ
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

State-ի թարմացումները միավորվում են

Երբ դուք կանչում եք setState(), React-ը միավորում է ձեր տրամադրած օբյեկտը ընթացիկ state-ի հետ։

Օրինակ, ձեր state-ը կարող է պարունակել մի քանի իրարից անկախ փոփոխականներ.

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

Այնուհետ, դուք կարող եք թարմացնել նրանց իրարից անկախ առանձին setState() կանչերով.

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

Միավորումը մակերեսային է, այսպիսով` this.setState({comments})-ը թողնում է this.state.posts-ին «անվնաս», բայց ամբողջությամբ փոխարինում է this.state.comments-ը։

Տվյալները հոսում են ներքև

Ո’չ ծնող, ո’չ զավակ կոմպոնենտները չեն կարող իմանալ, արդյո՞ք տվյալ կոմպոնենտը վիճակով(stateful) է կամ վիճակազուրկ(stateless), և նրանց չպետք է հետաքրքրի այն, թե դա հայտարարված է որպես ֆունկցիա, թե որպես կլաս։

Ահա թե ինչու state-ը հաճախ անվանվում է լոկալ կամ ինկապսուլացված։ Այն հասանելի չէ ուրիշ ոչ մի կոմպոնենտից, բացի այն մեկից, որին պատկանում է և որը տեղադրել է իրեն։

Կոմպոնենտը կարող է փոխանցել իր state-ը ներքև, որպես props, իր զավակ կոմպոնենտներին.

<h2>Ժամը {this.state.date.toLocaleTimeString()}-ն է։</h2>

Սա նաև աշխատում է օգտագործողի կողմից հայտարարված կոմպոնենտների համար.

<FormattedDate date={this.state.date} />

FormattedDate կոմպոնենտը կստանա date-ը իր props-ում և չի իմանա` այն եկել էր Clock-ի state-ից, Clock-ի props-ից, թե գրված էր ձեռքով.

function FormattedDate(props) {
  return <h2>Ժամը {props.date.toLocaleTimeString()}-ն է։</h2>;
}

Փորձել CodePen-ում

Սա սովորաբար կոչվում է «վերևից-ներքև» կամ «միակողմանի» տվյալների հոսք։ Ցանկացած state միշտ պատկանում է որևէ սպեցիֆիկ կոմպոնենտի, և ցանկացած տվյալ կամ UI ստացված այդ state-ից կարող է ազդել միայն ծառում դրանից «ներքև» գտնվող կոմպոնենտների վրա։

Եթե դուք պատկերացնեք կոմպոնենտների ծառը որպես prop-երի ջրվեժ, ապա ամեն կոմպոնենտի state նման է լրացուցիչ ջրի աղբյուրի, որը միանում է դրան կամայական կետում, բայց նաև հոսում է ներքև։

Որպեսզի ցույց տանք, որ բոլոր կոմպոնենտները իսկապես մեկուսացված են, մենք կարող ենք ստեղծել App կոմպոնենտ, որը կարտապատկերի երեք <Clock>։

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Փորձել CodePen-ում

Ամեն Clock-ում տեղադրված է իր սեփական ժամաչափը և թարմացվում է անկախ կերպով։

React հավելվածներում կոմպոնենտի վիճակով կամ առանց վիճակի լինելը համարվում է կոմպոնենտի իրականացման տարր, որը կարող է փոխվել ժամանակի ընթացքում։ Դուք կարող եք օգտագործել առանց վիճակի կոմպոնենտներ վիճակով կոմպոնենտի ներսում և հակառակը։