Lifting State Up
យើងសូមផ្ដល់អនុសាសន៍ពីការលើក state ដែលត្រូវបាន shared ទៅកាន់ ancestor រួមគ្នាដែលជិតស្និទ្ធបំផុត។ តោះយើងមើលរបៀបដែលវាដំណើរការ។
នៅក្នុងផ្នែកនេះ យើងនឹងបង្កើតម៉ាសុីនគណនាសីតុណ្ហាភាពដែលគណនាថាទឹកនឹងឆ្អិននៅសីតុណ្ហាភាពដែលបានផ្តល់ឱ្យ។
យើងនឹងចាប់ផ្តើមជាមួយ component មួយដែលត្រូវបានគេហៅថា BoilingVerdict
។
វាទទួលយកសីតុណ្ហាភាព celsius
ជា props ហើយ prints ថាតើវាគ្រប់គ្រាន់ដើម្បីដាំទឹក៖
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>; }
return <p>The water would not boil.</p>;}
បន្ទាប់មកទៀត យើងនឹងបង្កើត component មួយដែលត្រូវបានគេហៅថា Calculator
។ វា renders <input>
មួយដែលអនុញ្ញាតឱ្យអ្នកបញ្ចូលសីតុណ្ហភាព ហើយរក្សាតម្លៃរបស់វានៅក្នុង this.state.temperature
។
លើសពីនេះទៀត វា renders BoilingVerdict
សម្រាប់តម្លៃបញ្ចូលបច្ចុប្បន្ន។
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''}; }
handleChange(e) {
this.setState({temperature: e.target.value}); }
render() {
const temperature = this.state.temperature; return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset>
);
}
}
Adding a Second Input
តម្រូវការថ្មីរបស់យើងគឺថា បន្ថែមពីលើ Celsius input យើងផ្តល់ជូននូវ Fahrenheit input ហើយពួកវាត្រូវបានរក្សាទុកក្នុងលក្ខណះ sync។
យើងអាចចាប់ផ្តើមដោយការទាញយក TemperatureInput
component ពី Calculator
។ យើងនឹងបន្ថែម scale
prop ថ្មីមួយអោយវាដែលអាចជា "c"
ឬ "f"
៖
const scaleNames = { c: 'Celsius', f: 'Fahrenheit'};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale; return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
ឥឡូវនេះយើងអាចផ្លាស់ប្តូរ Calculator
ដើម្បី render temperature inputs ពីរដាច់ដោយឡែកពីគ្នា៖
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div>
);
}
}
ឥឡូវនេះយើងមាន inputs ចំនួនពីរ ប៉ុន្តែនៅពេលដែលអ្នកបញ្ចូលសីតុណ្ហភាពមួយក្នុងចំណោមពួកវា inputs ផ្សេងៗទៀតមិន update នេាះទេ។ នេះផ្ទុយនឹងតម្រូវការរបស់យើង៖ យើងចង់រក្សាវាក្នុងលក្ខណះ sync។
យើងក៏មិនអាចបង្ហាញផងដែរនូវ BoilingVerdict
ពី Calculator
។ Calculator
មិនដឹងពីសីតុណ្ហភាពបច្ចុប្បន្ ពីព្រោះវាត្រូវបានលាក់នៅខាងក្នុង TemperatureInput
។
Writing Conversion Functions
ដំបូង យើងនឹងសរសេរ functions ពីរដើម្បីបម្លែងពី Celsius ទៅជា Fahrenheit ហើយនិងពី Fahrenheit ទៅជា Celsius៖
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
functions ពីរនេះបម្លែងលេខ។ យើងនឹងសរសេរ function ផ្សេងទៀតដែលទទួលយក string temperature
ហើយនិង converter function មួយជា arguments ហើយ returns នូវ string មួយ។ យើងនឹងប្រើវាដើម្បីគណនាតម្លៃនៃ input មួយដោយផ្អែកលើ input ផ្សេង។
វា returns string ទទេមួយនៅលើ temperature
(សីតុណ្ហាភាព) មិនត្រឹមត្រួវមួយ ហើយវារក្សាទុក output (ទិន្នផល) ដែល rounded ទៅខ្ទង់ទសភាគទីបី៖
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
ឧទាហរណ៍ tryConvert('abc', toCelsius)
returns នូវ string ទទេមួយ ហើយ tryConvert('10.22', toFahrenheit)
returns នូវ '50.396'
។
Lifting State Up
បច្ចុប្បន្ន TemperatureInput
components ទាំងពីររក្សាតម្លៃរបស់ពួកគេនៅក្នុង local state ដោយឯករាជ្យ៖
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''}; }
handleChange(e) {
this.setState({temperature: e.target.value}); }
render() {
const temperature = this.state.temperature; // ...
ទោះយ៉ាងណា យើងចង់អោយ inputs ទាងំពីរនេះនៅក្នុងលក្ខណះ sync ជាមួយគ្នា។ នៅពេលដែលយើង update Celsius input, Fahrenheit input គួរតែ reflect (បង្ហាយអោយឃើញនូវ) សីតុណ្ហាភាពដែលបានបម្លែង, និងផ្ទុយមកវិញ។
នៅក្នុង React ការចែករំលែក state គឺត្រូវបានសម្រេចដោយការផ្លាស់ទីវាឡើងទៅកាន់ ancestor រួមគ្នាដែលជិតស្និទ្ធបំផុត នៃ components ដែលត្រូវការវា។ នេះត្រូវបានគេហៅថា “lifting state up”។ យើងនឹងដកចេញនូវ local state ពី TemperatureInput
ហើយផ្លាស់ទីវាទៅក្នុង Calculator
ជំនួសវិញ។
ប្រសិនបើ Calculator
មាន shared state ផ្ទាល់ខ្លួន, វាក្លាយជា “source of truth” សម្រាប់សីតុណ្ហាភាពបច្ចុប្បន្ននៅ inputs ទាំងពីរ។ វាអាចធ្វើអោយពួកវាទាំងពីរមានតម្លៃដែលស្របជាមួយគ្នា (consistent) ទៅវិញទៅមក។ ដែល props នៃ TemperatureInput
components ទាំងពីរគឺមកពី parent Calculator
component តែមួយ, inputs ទាំងពីរនឹងតែងតែមានលក្ខណះ sync។
តោះយើងមើលរបៀបដែលវាដំណើរការបានពីមួយជំហ៊ានទៅមួយជំហ៊ាន។
ដំបូង យើងនឹងជំនួស (replace) this.state.temperature
ជាមួយ this.props.temperature
ក្នុង TemperatureInput
component។ សម្រាប់ឥលូវនេះ ចូរក្លែងថា this.props.temperature
ធ្លាប់មានរួចហើយ, បើទោះបីជាយើងនឹងត្រូវការបេាះវាពី Calculator
នៅពេលអនាគត៖
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature; // ...
យើងដឹងថា props គឺ read-only។ នៅពេលដែល temperature
បាននៅក្នុង local state, TemperatureInput
គ្រាន់តែអាច call this.setState()
ដើម្បីផ្លាស់ប្តូរវា។ ទោះយ៉ាងណា, ពេលនេះ temperature
គឺមកពី parent ដែលជា prop, TemperatureInput
គ្មានការគ្រប់គ្រងលើវាទេ។
នៅក្នុង React, នេះត្រូវបានដោះស្រាយជាធម្មតាដោយការបង្កើត component “controlled”។ គ្រាន់តែដូច DOM <input>
ដែលទទួលយក (accepts) ទាំង value
ហើយនិង onChange
prop, ដូច្នេាះ custom TemperatureInput
អាចទទួលយក (accept) ទាំង temperature
និង onTemperatureChange
props ពី parent Calculator
របស់វា។
ឥឡូវនេះ, នៅពេលដែល TemperatureInput
ចង់ធ្វើបច្ចុប្បន្នភាព (update) temperature របស់វា, វា calls this.props.onTemperatureChange
៖
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value); // ...
ចំណាំ៖
មិនមានអត្ថន័យពិសេសចំពោះឈ្មេាះរបស់
temperature
ឬonTemperatureChange
prop នៅក្នុង custom components។ យើងអាចហៅពួកគេថាអ្វីផ្សេងទៀត, ដូចជាដាក់ឈ្មោះពួកvalue
និងonChange
ដែលវាជា convention រួមមួយ។
onTemperatureChange
prop នឹងត្រូវបានផ្តល់ឱ្យជាមួយគ្នានិង temperature
prop ដោយ parent Calculator
component។ វានឹង handle ការផ្លាស់ប្តូរដោយការកែប្រែ local state របស់ខ្លួនវាផ្ទាល់, ដូច្នេះការ re-render នូវ inputs ទាំងពីរជាមួយតម្លៃថ្មី។ យើងនឹងពិនិត្យមើលការអនុវត្ត (implementation) ថ្មីនៃ Calculator
ឆាប់ៗនេះ។
មុនពេលចូលទៅក្នុងការផ្លាស់ប្តូរក្នុង Calculator
, តោះពិនិត្យឡើងវិញនូវការផ្លាស់ប្តូររបស់ TemperatureInput
component។ យើងបានដកចេញនូវ local state ពីវា, ហើយនិងជំនួយដោយការអាន (read) this.state.temperature
, ឥឡូវយើងអាន (read) this.props.temperature
។ ជំនួសដោយការ call this.setState()
នៅពេលដែលយើងចង់ផ្លាស់ប្តូរ, ឥឡូវយើង call this.props.onTemperatureChange()
, ដែលនឹងត្រូវបានផ្តល់ដោយ Calculator
៖
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value); }
render() {
const temperature = this.props.temperature; const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
ឥឡូវនេះសូមត្រលប់ទៅ Calculator
component វិញ។
យើងនឹងរក្សាទុក input’s temperature
និង scale
បច្ចុប្បន្នក្នុង local state របស់វា។ នេះគឺជា state យើង “lifted up” ពី inputs, ហើយវានឹងបម្រើជា “source of truth” សម្រាប់ពួកវាទាំងពីរ។ វាជាតំណាងតូចបំផុតនៃទិន្នន័យទាំងអស់ដែលយើងត្រូវដឹងក្នុងគោលបំណង render inputs ទាំងពីរ។
ឧទាហរណ៍, ប្រសិនបើយើងបញ្ចូល ៣៧ ទៅក្នុង Celsius input, state នៃ Calculator
component នឹងទៅជា៖
{
temperature: '37',
scale: 'c'
}
ប្រសិនបើយើងកែសម្រួល Fahrenheit field ទៅជា ២១២ នៅពេលក្រោយ, state នៃ Calculator
នឹងទៅជា៖
{
temperature: '212',
scale: 'f'
}
យើងអាចរក្សាទុកតម្លៃនៃ inputs ទាំងពីរ, ប៉ុន្តែវាប្រែទៅជាមិនចាំបាច់។ វាគ្រប់គ្រាន់ក្នុងការរក្សាទុកតម្លៃនៃ input ដែលបានផ្លាស់ប្តូរថ្មីបំផុត, និងទំហំដែលវាតំណាងអោយ។ យើងអាចសន្និដ្ឋានបាននូវតម្លៃនៃ input ផ្សេងដោយផ្អែកលើ temperature
និង scale
តែឯង។
Inputs មានលក្ខណះជា sync ពីព្រេាះ តម្លៃរបស់ពួកវាត្រូវបានគណនាពី state ដូចគ្នា៖
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'}; }
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature}); }
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature}); }
render() {
const scale = this.state.scale; const temperature = this.state.temperature; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius} onTemperatureChange={this.handleCelsiusChange} /> <TemperatureInput
scale="f"
temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange} /> <BoilingVerdict
celsius={parseFloat(celsius)} /> </div>
);
}
}
ឥឡូវនេះ, មិនថា input មួយណាដែលអ្នកកែប្រែ, this.state.temperature
ហើយនិង this.state.scale
នៅក្នុង Calculator
និងទទួលបានការធ្វើបច្ចុប្បន្នភាព។ មួយនៃ inputs ទទួលបានតម្លៃដូច, ដូច្នេះ input របស់អ្នកប្រើប្រាស់ត្រូវបានរក្សាទុក, ហើយ តម្លៃរបស់ input ផ្សេងត្រូវបានគណនាឡើងវិញដោយផ្អែកលើវាជានិច្ច។
ចូរសង្ខេបនូវអ្វីដែលកើតឡើងនៅពេលអ្នកកែប្រែការ input៖
- React calls function ដែលបានបញ្ជាក់គឺ
onChange
នៅលើ DOM<input>
។ នៅក្នុងករណីរបស់យើង, នេះគឺhandleChange
method នៅក្នុងTemperatureInput
component។ handleChange
method នៅក្នុងTemperatureInput
component callsthis.props.onTemperatureChange()
ជាមួយនឹងតម្លៃថ្មីដែលចង់បាន។ props របស់វា, រួមទាំងonTemperatureChange
, ត្រូវបានផ្តល់ដោយ parent component របស់វាគឺCalculator
។- នៅពេលដែលវាបាន render ពីមុន,
Calculator
បានបញ្ជាក់ថាonTemperatureChange
នៃTemperatureInput
របស់ Celsius គឺជាhandleCelsiusChange
method របស់Calculator
, ហើយonTemperatureChange
នៃ FahrenheitTemperatureInput
គឺជាhandleFahrenheitChange
method របស់Calculator
។ ដូច្នេះ methods ទាំងពីររបស់Calculator
ត្រូវបាន call ដោយផ្អែកលើការ input ដែលយើងបានកែសម្រួល។ - នៅក្នុង methods ទាំងនេះ,
Calculator
component ស្នើរ React ដើម្បី re-render ខ្លួនវាដោយការ callthis.setState()
ជាមួយតម្លៃថ្មីរបស់ input និង scale បច្ចុប្បន្ននៃ input ដែលយើងទើបតែកែសម្រួល។ - React calls
render
method របស់Calculator
component ដើម្បីស្វែងយល់ថា តើ UI គួរមើលឃើញដូចម្តេច។ ម្លៃនៃ inputs ទាំងពីរត្រូវបានគណនាឡើងវិញផ្អែកលើ temperature បច្ចុប្បន្ននិង active scale។ ការបម្លែងសីតុណ្ហភាពត្រូវបានអនុវត្តនៅទីនេះ។ - React calls
render
methods នៃTemperatureInput
components ផ្ទាល់ខ្លួនជាមួយ props ថ្មីរបស់ពួកវាដែលបានបញ្ជាក់ដោយCalculator
។ វាដឹងថា UI របស់ពួកគេគួរតែមានលក្ខណៈដូចម្តេច។ - React calls
render
method នៃBoilingVerdict
component, ដោយការបេាះ temperature នៅក្នុង Celsius ជា props របស់វា។ - React DOM ធ្វើបច្ចុប្បន្នភាព DOM ជាមួយ boiling verdict ហើយនិងដើម្បីផ្គូផ្គងតម្លៃ input ដែលចង់បាន។ input ដែលយើងបានកែតម្រូវទទួលបានតម្លៃបច្ចុប្បន្នរបស់វា, ហើយ input ផ្សេងៗត្រូវបានធ្វើឱ្យទាន់សម័យទៅសីតុណ្ហភាពបន្ទាប់ពីបម្លែង។
រាល់ការធ្វើបច្ចុប្បន្នភាពត្រូវឆ្លងកាត់ជំហានដូចគ្នាដូច្នេះ input នូវរក្សារលក្ខណះ sync។
Lessons Learned
វាគួរតែមាន “source of truth” តែមួយសម្រាប់ទិន្នន័យណាមួយដែលផ្លាស់ប្តូរនៅក្នុង React application។ ជាធម្មតា, state ត្រូវបានបន្ថែមជាលើកដំបូងទៅអោយ component ដែលត្រូវការវាសម្រាប់ការ render។ ប្រសិនបើ components ផ្សេងៗត្រូវការវាដែរ, អ្នកអាចលើកវាទៅកាន់ក្រុម ancestor ដែលជិតបំផុតរបស់វា។ ជំនួសឱ្យការព្យាយាម sync state រវាង components ខុសគ្នា, អ្នកគួរតែពឹងផ្អែកលើ top-down data flow។
ការលើក state ជាប់ពាក់ព័ន្ធនឹងការសរសេរបន្ថែម code “boilerplate” ច្រើនជាង វិធីសាស្រ្ two-way binding, ប៉ុន្តែជាប្រយោជន៍មួយ, វាធ្វើការតិចតួចដើម្បីរកនិងកម្ចាត់ bugs។ ដែល state មួយចំនួន “lives” នៅក្នុង component ខ្លះហើយ component នេាះអាចផ្លាស់ប្តូរវាបាន, bugs ត្រូវបានកាត់បន្ថយយ៉ាងខ្លាំង។ លើសពីនេះទៀត, អ្នកអាច impement custom logic មួយចំនួនដើម្បីបដិសេធឬបំលែងការបញ្ចូលរបស់អ្នកប្រើ។
ប្រសិនបើមានអ្វីអាចទាញចេញពី props ឬក័ state, វាប្រហែលជាមិនគួរនៅក្នុង state, ឧទាហរណ៍, ជំនួសឱ្យការរក្សាទុកទាំង celsiusValue
និង fahrenheitValue
, យើងរក្សាទុកតែការកែប្រែចុងក្រោយនូវ temperature
និង scale
របស់វាប៉ុណ្ណោះ។ តម្លៃនៃការ input ផ្សេងៗអាច
តែងតែអាចគណនាពីពួកវាបាននៅក្នុង render
method។ នេះអនុញ្ញាតឱ្យយើងសម្អាត (clear) ឬអនុវត្ត (apply) field ផ្សេង ដោយមិនបាត់បង់ precision ក្នុងការបញ្ចូលរបស់អ្នកប្រើប្រាស់។
នៅពេលដែលអ្នកឃើញអ្វីមួយខុសនៅក្នុង UI, អ្នកអាចប្រើ React Developer Tools ដើម្បី inspect props ហើយនិង move up the tree រហូតដល់អ្នករកឃើញ component ដែលទទួលខុសត្រូវសម្រាប់ធ្វើបច្ចុប្បន្នភាព state។ នេះអនុញ្ញាតឱ្យអ្នកតាមដានកំហុសទៅកាន់ប្រភពរបស់វា៖
