рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛: рд╕реНрдЯреЗрдЯ рдЕрдк рдХрд░рдирд╛ рдЖрдкрдХреЗ рдРрдк рдХреЛ рдорд╛рд░ рд░рд╣рд╛ рд╣реИ

рдЖрд╡рд░рдг


рдХреНрдпрд╛ рдЖрдкрдиреЗ "рд░рд╛рдЬреНрдп рдХреЛ рдКрдкрд░ рдЙрдард╛рдиреЗ" рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реБрдирд╛ рд╣реИ? рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рд╣реИ рдФрд░ рдпрд╣реА рдХрд╛рд░рдг рд╣реИ рдХрд┐ рдЖрдк рдпрд╣рд╛рдБ рд╣реИрдВред рдпрд╣ рдХреИрд╕реЗ рд╕рдВрднрд╡ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ рдХрд┐ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рджрд╕реНрддрд╛рд╡реЗрдЬ рдореЗрдВ рд╕реВрдЪреАрдмрджреНрдз 12 рдореБрдЦреНрдп рдЕрд╡рдзрд╛рд░рдгрд╛рдУрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдЦрд░рд╛рдм рдкреНрд░рджрд░реНрд╢рди рдХрд╛ рдХрд╛рд░рдг рд╣реЛ рд╕рдХрддрд╛ рд╣реИ? рдЗрд╕ рд▓реЗрдЦ рдХреЗ рднреАрддрд░, рд╣рдо рдПрдХ рд╕реНрдерд┐рддрд┐ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВрдЧреЗ рдЬрдм рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдРрд╕рд╛ рд╣реЛред


рдЪрд░рдг 1: рдЗрд╕реЗ рдКрдкрд░ рдЙрдард╛рдПрдВ


рдореИрдВ рдЖрдкрдХреЛ рдЯрд┐рдХ-рдЯреИрдХ-рдЯреЛ рдХрд╛ рдПрдХ рд╕рд░рд▓ рдЧреЗрдо рдмрдирд╛рдиреЗ рдХрд╛ рд╕реБрдЭрд╛рд╡ рджреЗрддрд╛ рд╣реВрдВред рдЦреЗрд▓ рдХреЗ рд▓рд┐рдП рд╣рдореЗрдВ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреА:


  • рдХреБрдЫ рдЦреЗрд▓ рд░рд╛рдЬреНрдпред рдХреЛрдИ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдЦреЗрд▓ рддрд░реНрдХ рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдирд╣реАрдВ рдХрд┐ рд╣рдо рдЬреАрддрддреЗ рд╣реИрдВ рдпрд╛ рд╣рд╛рд░рддреЗ рд╣реИрдВред рдмрд╕ рдПрдХ рд╕рд░рд▓ рджреНрд╡рд┐-рдЖрдпрд╛рдореА рд╕рд░рдгреА рдЬреЛ рдпрд╛ рддреЛ undefined , "x" рдпрд╛ "0".


     const size = 10 // Two-dimensional array (size * size) filled with `undefined`. Represents an empty field. const initialField = new Array(size).fill(new Array(size).fill(undefined)) 

  • рд╣рдорд╛рд░реЗ рдЦреЗрд▓ рдХреА рд╕реНрдерд┐рддрд┐ рдХреЛ рд╣реЛрд╕реНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдореВрд▓ рдХрдВрдЯреЗрдирд░ред


     const App = () => { const [field, setField] = useState(initialField) return ( <div> {field.map((row, rowI) => ( <div> {row.map((cell, cellI) => ( <Cell content={cell} setContent={ // Update a single cell of a two-dimensional array // and return a new two dimensional array (newContent) => setField([ // Copy rows before our target row ...field.slice(0, rowI), [ // Copy cells before our target cell ...field[rowI].slice(0, cellI), newContent, // Copy cells after our target cell ...field[rowI].slice(cellI + 1), ], // Copy rows after our target row ...field.slice(rowI + 1), ]) } /> ))} </div> ))} </div> ) } 

  • рдПрдХ рдПрдХрд▓ рдХреЛрд╢рд┐рдХрд╛ рдХреА рд╕реНрдерд┐рддрд┐ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдмрдЪреНрдЪрд╛ рдШрдЯрдХред


     const randomContent = () => (Math.random() > 0.5 ? 'x' : '0') const Cell = ({ content, setContent }) => ( <div onClick={() => setContent(randomContent())}>{content}</div> ) 


рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 1


рдЕрдм рддрдХ рдпрд╣ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рджрд┐рдЦрддрд╛ рд╣реИред рдПрдХ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рд╢реАрд▓ рдХреНрд╖реЗрддреНрд░ рдЬрд┐рд╕реЗ рдЖрдк рдкреНрд░рдХрд╛рд╢ рдХреА рдЧрддрд┐ рд╕реЗ рдмрд╛рддрдЪреАрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ :) рдЪрд▓реЛ рдЖрдХрд╛рд░ рдмрдврд╝рд╛рддреЗ рд╣реИрдВред рдХрд╣рддреЗ рд╣реИрдВ, 100. рд╣рд╛рдБ, рдпрд╣ рдЙрд╕ рдбреЗрдореЛ рд▓рд┐рдВрдХ рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░рдиреЗ рдФрд░ рдмрд╣реБрдд рд╢реАрд░реНрд╖ рдкрд░ size рдЪрд░ рдмрджрд▓рдиреЗ рдХрд╛ рд╕рдордп рд╣реИред рдЕрднреА рднреА рдЖрдк рдХреЗ рд▓рд┐рдП рдЙрдкрд╡рд╛рд╕? 200 рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВ рдпрд╛ рдХреНрд░реЛрдо рдореЗрдВ рдирд┐рд░реНрдорд┐рдд рд╕реАрдкреАрдпреВ рдереНрд░реЙрдЯрд▓рд┐рдВрдЧ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред рдХреНрдпрд╛ рдЖрдк рдХрд┐рд╕реА рд╕реЗрд▓ рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░рдиреЗ рдХреЗ рд╕рдордп рдФрд░ рдЙрд╕рдХреА рд╕рд╛рдордЧреНрд░реА рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рд╕рдордп рдХреЗ рдмреАрдЪ рдЕрдм рдПрдХ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдЕрдВрддрд░рд╛рд▓ рджреЗрдЦрддреЗ рд╣реИрдВ?


рдЖрдЗрдП size рд╡рд╛рдкрд╕ 10 рдореЗрдВ рдмрджрд▓реЗрдВ рдФрд░ рдХрд╛рд░рдг рдХреА рдЬрд╛рдВрдЪ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рд░реВрдкрд░реЗрдЦрд╛ рдЬреЛрдбрд╝реЗрдВред


 const Cell = ({ content, setContent }) => { console.log('cell rendered') return <div onClick={() => setContent(randomContent())}>{content}</div> } 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 2


рд╣рд╛рдБ, рдпрд╣ рдмрд╛рдд рд╣реИред рдпрд╣ рд╣рд░ рд░реЗрдВрдбрд░ рдкрд░ рдЪрд▓рдиреЗ рдХреЗ рд╕рд╛рде рд╕рд┐рдВрдкрд▓ console.log рдкрд░реНрдпрд╛рдкреНрдд рд╣реЛрдЧрд╛ред


рддреЛ рд╣рдо рдХреНрдпрд╛ рджреЗрдЦрддреЗ рд╣реИрдВ? рд╣рдорд╛рд░реЗ рдХрдВрд╕реЛрд▓ рдореЗрдВ "рд╕реЗрд▓ рд░реЗрдВрдбрд░" рд╕реНрдЯреЗрдЯрдореЗрдВрдЯ ( size = рдПрди рдпрд╣ рдПрди рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП) рдкрд░ рд╕рдВрдЦреНрдпрд╛ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдЬреИрд╕реЗ рдкреВрд░реЗ рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рд╣рд░ рдмрд╛рд░ рдПрдХрд▓ рд╕реЗрд▓ рдореЗрдВ рдмрджрд▓рд╛рд╡ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред


рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдмрд╛рдд рдпрд╣ рд╣реИ рдХрд┐ рд░рд┐рдПрдХреНрдЯ рдкреНрд░рд▓реЗрдЦрди рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдХреБрдЫ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХреЛ рдЬреЛрдбрд╝рдирд╛ рд╣реИ ред


 <div> {field.map((row, rowI) => ( <div key={rowI}> {row.map((cell, cellI) => ( <Cell key={`row${rowI}cell${cellI}`} content={cell} setContent={(newContent) => setField([ ...field.slice(0, rowI), [ ...field[rowI].slice(0, cellI), newContent, ...field[rowI].slice(cellI + 1), ], ...field.slice(rowI + 1), ]) } /> ))} </div> ))} </div> 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 3


рд╣рд╛рд▓рд╛рдБрдХрд┐, рдлрд┐рд░ рд╕реЗ size рдмрдврд╝рдиреЗ рдХреЗ рдмрд╛рдж рд╣рдо рджреЗрдЦрддреЗ рд╣реИрдВ рдХрд┐ рд╕рдорд╕реНрдпрд╛ рдЕрднреА рднреА рд╣реИред рдпрджрд┐ рдХреЗрд╡рд▓ рд╣рдо рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдХреЛрдИ рдШрдЯрдХ рдХреНрдпреЛрдВ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ ... рд╕реМрднрд╛рдЧреНрдп рд╕реЗ, рд╣рдо рдЕрджреНрднреБрдд рд░рд┐рдПрдХреНрдЯ DevTools рдХреА рдХреБрдЫ рдорджрдж рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдпрд╣ рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реИ рдХрд┐ рдШрдЯрдХ рдХреНрдпреЛрдВ рдкреНрд░рджрд╛рди рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВред рд╣рд╛рд▓рд╛рдВрдХрд┐ рдЖрдкрдХреЛ рдЗрд╕реЗ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рд╕рдХреНрд╖рдо рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред


DevTools рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЛ рдкреБрдирдГ рд╕рдХреНрд░рд┐рдп рдХрд░реЗрдВ


рдПрдХ рдмрд╛рд░ рдЬрдм рдпрд╣ рд╕рдХреНрд╖рдо рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рд╣рдо рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рд╕рднреА рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЛ рдлрд┐рд░ рд╕реЗ рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ рдХреНрдпреЛрдВрдХрд┐ рдЙрдирдХрд╛ рд╕рд╣рд╛рд░рд╛ рдмрджрд▓ рдЧрдпрд╛ рдерд╛, рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ, setContent рдкреНрд░реЛрдкред


рд░рд┐рдПрдХреНрдЯ DevTools рд░рд┐рдкреЛрд░реНрдЯ # 1


рдкреНрд░рддреНрдпреЗрдХ рд╕реЗрд▓ рдореЗрдВ рджреЛ рдкреНрд░реЙрдкреНрд╕ рд╣реЛрддреЗ рд╣реИрдВ: content рдФрд░ рд╕реЗрдЯ- content ред рдпрджрд┐ рд╕реЗрд▓ [0] [0] рдмрджрд▓рддрд╛ рд╣реИ, рд╕реЗрд▓ рдХреА рд╕рд╛рдордЧреНрд░реА [0] [1] рдирд╣реАрдВ рдмрджрд▓рддреА рд╣реИред рджреВрд╕рд░реА рдУрд░, cellI field , cellI рдФрд░ rowI рдХреЛ рдмрдВрдж рдХрд░ рджреЗрддрд╛ рд╣реИред cellI рдФрд░ rowI рдПрдХ рд╕рдорд╛рди рд░рд╣рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдХрд┐рд╕реА рднреА рд╕реЗрд▓ рдХреЗ рдкреНрд░рддреНрдпреЗрдХ рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рд╕рд╛рде field рдкрд░рд┐рд╡рд░реНрддрди рд╣реЛрддрд╛ рд╣реИред


рдЖрдЗрдП рд╣рдорд╛рд░реЗ рдХреЛрдб рдХреЛ рд░реАрдлреИрдХреНрдЯрд░ рдХрд░реЗрдВ рдФрд░ рд╕реЗрдЯ рдХреЛ рдПрдХ рд╕рдорд╛рди рд░рдЦреЗрдВред


рд╕рдВрджрд░реНрдн рдХреЛ рд╕реЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрд╕реА рдХреЛ рдмрдирд╛рдП рд░рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдореЗрдВ рдХреНрд▓реЛрдЬрд░ рд╕реЗ рдЫреБрдЯрдХрд╛рд░рд╛ рдкрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рд╣рдо рдЕрдкрдиреЗ Cell рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдкрд╛рд╕ рдХрд░рдХреЗ cellI рдФрд░ rowI рдмрдВрдж рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред field рд░реВрдк рдореЗрдВ, рд╣рдо setState рдХреА рдПрдХ рд╕рд╛рдл рд╕реБрд╡рд┐рдзрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ - рдпрд╣ рдХреЙрд▓рдмреИрдХ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИ ред


 const [field, setField] = useState(initialField) // `useCallback` keeps reference to `setCell` the same. const setCell = useCallback( (rowI, cellI, newContent) => setField((oldField) => [ ...oldField.slice(0, rowI), [ ...oldField[rowI].slice(0, cellI), newContent, ...oldField[rowI].slice(cellI + 1), ], ...oldField.slice(rowI + 1), ]), [], ) 

рдЬрд┐рд╕рд╕реЗ App рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ


 <div> {field.map((row, rowI) => ( <div key={rowI}> {row.map((cell, cellI) => ( <Cell key={`row${rowI}cell${cellI}`} content={cell} rowI={rowI} cellI={cellI} setContent={setCell} /> ))} </div> ))} </div> 

рдЕрдм Cell рдХреЛ cellI рдФрд░ rowI рдХреЛ rowI рдкрд╛рд╕ рдХрд░рдирд╛ рд╣реИред


 const Cell = ({ content, rowI, cellI, setContent }) => { console.log('cell render') return ( <div onClick={() => setContent(rowI, cellI, randomContent())}> {content} </div> ) } 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 4


рдЖрдЗрдП DevTools рд░рд┐рдкреЛрд░реНрдЯ рдкрд░ рдПрдХ рдирдЬрд╝рд░ рдбрд╛рд▓реЗрдВред


рд░рд┐рдПрдХреНрдЯ DevTools рд░рд┐рдкреЛрд░реНрдЯ # 2


рдХреНрдпрд╛! рдмрд┐рд▓реНрд▓реА рдХреНрдпреЛрдВ рдХрд╣рддреА рд╣реИ "рдорд╛рддрд╛-рдкрд┐рддрд╛ рдХреЗ рдмрджрд▓реЗ рд╣реБрдП рдЧреБрдг"? рддреЛ рдмрд╛рдд рдпрд╣ рд╣реИ рдХрд┐ рд╣рд░ рдмрд╛рд░ рдЬрдм рд╣рдорд╛рд░реЗ рдХреНрд╖реЗрддреНрд░ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рддреЛ App рдХреЛ рдлрд┐рд░ рд╕реЗ рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП рдЗрд╕рдХреЗ рдмрд╛рд▓ рдШрдЯрдХреЛрдВ рдХреЛ рдлрд┐рд░ рд╕реЗ рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдареАрдХ рд╣реИред рдХреНрдпрд╛ stackoverflow React рдкреНрд░рджрд░реНрд╢рди рдЕрдиреБрдХреВрд▓рди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдХреБрдЫ рдЙрдкрдпреЛрдЧреА рдХрд╣рддрд╛ рд╣реИ? рдЗрдВрдЯрд░рдиреЗрдЯ рдХрд╛ рд╕реБрдЭрд╛рд╡ рджреЗрдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд╛ рдЙрдкрдпреЛрдЧ shouldComponentUpdate рдпрд╛ рдЗрд╕рдХреЗ рдХрд░реАрдмреА рд░рд┐рд╢реНрддреЗрджрд╛рд░реЛрдВ: PureComponent рдФрд░ memo ред


 const Cell = memo(({ content, rowI, cellI, setContent }) => { console.log('cell render') return ( <div onClick={() => setContent(rowI, cellI, randomContent())}> {content} </div> ) }) 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 5


рдЖрд╣рд╛! рдЕрдм рдХреЗрд╡рд▓ рдПрдХ рд╕реЗрд▓ рдХрд╛ рдХрдВрдЯреЗрдВрдЯ рдмрджрд▓рдиреЗ рдХреЗ рдмрд╛рдж рдлрд┐рд░ рд╕реЗ рдкреЗрд╢ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рд▓реЗрдХрд┐рди рд░реБрдХрд┐рдП ... рдХреНрдпрд╛ рдХреЛрдИ рдЖрд╢реНрдЪрд░реНрдп рд╣реБрдЖ? рд╣рдордиреЗ рд╕рд░реНрд╡реЛрддреНрддрдо рдкреНрд░рдерд╛рдУрдВ рдХрд╛ рдкрд╛рд▓рди рдХрд┐рдпрд╛ рдФрд░ рдЕрдкреЗрдХреНрд╖рд┐рдд рдкрд░рд┐рдгрд╛рдо рдорд┐рд▓рд╛ред


рдПрдХ рджреБрд╖реНрдЯ рд╣рдВрд╕реА рдпрд╣рд╛рдБ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдерд╛ред рдЬреИрд╕рд╛ рдХрд┐ рдореИрдВ рдЖрдкрдХреЗ рд╕рд╛рде рдирд╣реАрдВ рд╣реВрдВ, рдХреГрдкрдпрд╛, рдЗрд╕рдХреА рдХрд▓реНрдкрдирд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдпрдерд╛рд╕рдВрднрд╡ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред рдЖрдЧреЗ рдмрдврд╝реЗрдВ рдФрд░ рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 5 рдореЗрдВ size рдмрдврд╝рд╛рдПрдВред рдЗрд╕ рдмрд╛рд░ рдЖрдкрдХреЛ рдереЛрдбрд╝реА рдмрдбрд╝реА рд╕рдВрдЦреНрдпрд╛ рдХреЗ рд╕рд╛рде рдЬрд╛рдирд╛ рдкрдбрд╝ рд╕рдХрддрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЕрдВрддрд░рд╛рд▓ рдЕрднреА рднреА рд╣реИред рдХреНрдпреЛрдВ ???


рдЪрд▓рд┐рдП рдПрдХ рдмрд╛рд░ рдлрд┐рд░ рд╕реЗ DebTools рд░рд┐рдкреЛрд░реНрдЯ рдкрд░ рдирдЬрд╝рд░ рдбрд╛рд▓рддреЗ рд╣реИрдВред


рд░рд┐рдПрдХреНрдЯ DevTools рд░рд┐рдкреЛрд░реНрдЯ # 3


Cell рдХрд╛ рдХреЗрд╡рд▓ рдПрдХ рд╣реА рд░реЗрдВрдбрд░ рд╣реИ рдФрд░ рдпрд╣ рдмрд╣реБрдд рддреЗрдЬрд╝ рдерд╛, рд▓реЗрдХрд┐рди App рдХрд╛ рдПрдХ рд░реЗрдВрдбрд░ рднреА рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рдХрд╛рдлреА рд╕рдордп рд▓рдЧрд╛ред рдмрд╛рдд рдпрд╣ рд╣реИ рдХрд┐ App рдкреБрди: рд░реЗрдВрдбрд░ рдХреЗ рд╕рд╛рде рдкреНрд░рддреНрдпреЗрдХ Cell рдХреЛ рдЕрдкрдиреЗ рдкрд┐рдЫрд▓реЗ рдкреНрд░реЙрдкреНрд╕ рдХреЗ рд╕рд╛рде рдЕрдкрдиреЗ рдирдП рдкреНрд░реЙрдкреНрд╕ рдХреА рддреБрд▓рдирд╛ рдХрд░рдиреА рд╣реЛрдЧреАред рднрд▓реЗ рд╣реА рдпрд╣ рд░реЗрдВрдбрд░ рди рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд░рддрд╛ рд╣реИ (рдЬреЛ рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ рдареАрдХ рд╣реИ), рдХрд┐ рддреБрд▓рдирд╛ рдореЗрдВ рдЕрднреА рднреА рд╕рдордп рд▓рдЧрддрд╛ рд╣реИред O (1), рд▓реЗрдХрд┐рди O (1) рдХрд╛ size * size рдмрд╛рд░ рд╣реЛрддрд╛ рд╣реИ!


рдЪрд░рдг 2: рдЗрд╕реЗ рдиреАрдЪреЗ рд▓реЗ рдЬрд╛рдПрдБ


рд╣рдо рдЗрд╕рдХреЗ рдЖрд╕рдкрд╛рд╕ рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреНрдпрд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ? рдпрджрд┐ рд░реЗрдВрдбрд░рд┐рдВрдЧ App рдХреАрдордд рд╣рдореЗрдВ рдмрд╣реБрдд рдЕрдзрд┐рдХ рд╣реИ, рддреЛ рд╣рдореЗрдВ App рд░реЗрдВрдбрд░ рдХрд░рдирд╛ рдмрдВрдж рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдпрд╣ рд╕рдВрднрд╡ рдирд╣реАрдВ рд╣реИ рдЕрдЧрд░ useState рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ App рдореЗрдВ рд╣рдорд╛рд░реЗ рд░рд╛рдЬреНрдп рдХреЛ рд╣реЛрд╕реНрдЯ рдХрд░рддреЗ useState , рдХреНрдпреЛрдВрдХрд┐ рдпрд╣реА рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдкреБрди: useState рдЯреНрд░рд┐рдЧрд░ рдХрд░рддрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдЕрдкрдиреЗ рд░рд╛рдЬреНрдп рдХреЛ рдиреАрдЪреЗ рд▓реЗ рдЬрд╛рдирд╛ рд╣реЛрдЧрд╛ рдФрд░ рдкреНрд░рддреНрдпреЗрдХ Cell рдХреЛ рд░рд╛рдЬреНрдп рдХреЛ рдЕрдкрдиреА рд╕рджрд╕реНрдпрддрд╛ рджреЗрдиреА рд╣реЛрдЧреАред


рдЖрдЗрдП рдПрдХ рд╕рдорд░реНрдкрд┐рдд рд╡рд░реНрдЧ рдмрдирд╛рдПрдВ рдЬреЛ рд╣рдорд╛рд░реЗ рд░рд╛рдЬреНрдп рдХреЗ рд▓рд┐рдП рдПрдХ рдХрдВрдЯреЗрдирд░ рд╣реЛрдЧрд╛ред


 class Field { constructor(fieldSize) { this.size = fieldSize // Copy-paste from `initialState` this.data = new Array(this.size).fill(new Array(this.size).fill(undefined)) } cellContent(rowI, cellI) { return this.data[rowI][cellI] } // Copy-paste from old `setCell` setCell(rowI, cellI, newContent) { console.log('setCell') this.data = [ ...this.data.slice(0, rowI), [ ...this.data[rowI].slice(0, cellI), newContent, ...this.data[rowI].slice(cellI + 1), ], ...this.data.slice(rowI + 1), ] } map(cb) { return this.data.map(cb) } } const field = new Field(size) 

рддрдм рд╣рдорд╛рд░рд╛ App рдЗрд╕ рддрд░рд╣ рджрд┐рдЦ рд╕рдХрддрд╛ рдерд╛:


 const App = () => { return ( <div> {// As you can see we still need to iterate over our state to get indexes. field.map((row, rowI) => ( <div key={rowI}> {row.map((cell, cellI) => ( <Cell key={`row${rowI}cell${cellI}`} rowI={rowI} cellI={cellI} /> ))} </div> ))} </div> ) } 

рдФрд░ рд╣рдорд╛рд░рд╛ Cell рдЕрдкрдиреЗ рджрдо рдкрд░ field рд╕реЗ рд╕рд╛рдордЧреНрд░реА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░ рд╕рдХрддрд╛ рд╣реИ:


 const Cell = ({ rowI, cellI }) => { console.log('cell render') const content = field.cellContent(rowI, cellI) return ( <div onClick={() => field.setCell(rowI, cellI, randomContent())}> {content} </div> ) } 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 6


рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░, рд╣рдо рдЕрдкрдиреЗ рдХреНрд╖реЗрддреНрд░ рдХрд╛ рдкреНрд░рддрд┐рдкрд╛рджрди рдХрд░рддреЗ рд╣реБрдП рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВред рд╣рд╛рд▓рд╛рдБрдХрд┐, рдпрджрд┐ рд╣рдо рд╕реЗрд▓ рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдХреБрдЫ рднреА рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИред рд▓реЙрдЧ рдореЗрдВ рд╣рдо рдкреНрд░рддреНрдпреЗрдХ рдХреНрд▓рд┐рдХ рдХреЗ рд▓рд┐рдП "setCell" рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╕реЗрд▓ рдЦрд╛рд▓реА рд░рд╣рддрд╛ рд╣реИред рдпрд╣рд╛рдБ рдХрд╛рд░рдг рдпрд╣ рд╣реИ рдХрд┐ рдХреБрдЫ рднреА рд╕реЗрд▓ рдХреЛ рдлрд┐рд░ рд╕реЗ рдкреНрд░рд╕реНрддреБрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдирд╣реАрдВ рдХрд╣рддрд╛ рд╣реИред рд░рд┐рдПрдХреНрдЯ рдХреЗ рдмрд╛рд╣рд░ рд╣рдорд╛рд░рд╛ рд░рд╛рдЬреНрдп рдмрджрд▓рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд░рд┐рдПрдХреНрдЯ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдирд╣реАрдВ рдЬрд╛рдирддрд╛ рд╣реИред рдЬрд┐рд╕реЗ рдмрджрд▓рдирд╛ рд╣реИред


рд╣рдо рдкреНрд░реЛрдЧреНрд░рд╛рдореЗрдЯрд┐рдХ рд░реВрдк рд╕реЗ рд░реЗрдВрдбрд░ рдХреИрд╕реЗ рдЯреНрд░рд┐рдЧрд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ?


рдХрдХреНрд╖рд╛рдУрдВ рдХреЗ рд╕рд╛рде рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдмрд▓ рд╣реИ ред рдХреНрдпрд╛ рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╣рдореЗрдВ рдЕрдкрдиреЗ рдХреЛрдб рдХреЛ рдХрдХреНрд╖рд╛рдУрдВ рдореЗрдВ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрдирд╛ рд╣реЛрдЧрд╛? рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдирд╣реАрдВред рд╣рдо рдХрд╛рд░реНрдпрд╛рддреНрдордХ рдШрдЯрдХреЛрдВ рдХреЗ рд╕рд╛рде рдХреНрдпрд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХреБрдЫ рдбрдореА рд░рд╛рдЬреНрдп рдХреЛ рдкреЗрд╢ рдХрд░рдирд╛ рд╣реИ, рдЬрд┐рд╕реЗ рд╣рдо рдХреЗрд╡рд▓ рдЕрдкрдиреЗ рдШрдЯрдХ рдХреЛ рдлрд┐рд░ рд╕реЗ рдкреНрд░рд╕реНрддреБрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдордЬрдмреВрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрджрд▓рддреЗ рд╣реИрдВред


рдпрд╣рд╛рдВ рдмрддрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ рдХрд┐ рд╣рдо рд░реА-рд░реЗрдВрдбрд░реНрд╕ рдХреЛ рдмрд╛рдзреНрдп рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдХрд╕реНрдЯрдо рд╣реБрдХ рдХреИрд╕реЗ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВред


 const useForceRender = () => { const [, setDummy] = useState(0) const forceRender = useCallback(() => setDummy((oldVal) => oldVal + 1), []) return forceRender } 

рд╣рдорд╛рд░реЗ рдХреНрд╖реЗрддреНрд░ рдХреЗ рдЕрдкрдбреЗрдЯ рдХреЗ рд▓рд┐рдП рдПрдХ рд░реЗрдВрдбрд░ рд░реЗрдВрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЬрдм рд╣рдо рдЕрдкрдбреЗрдЯ рдХрд░рддреЗ рд╣реИрдВ рддреЛ рд╣рдореЗрдВ рдпрд╣ рдЬрд╛рдирдирд╛ рд╣реЛрддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╣рдореЗрдВ рдХрд┐рд╕реА рддрд░рд╣ рд╕реЗ рдлреАрд▓реНрдб рдЕрдкрдбреЗрдЯ рдХреЗ рд▓рд┐рдП рд╕рдмреНрд╕рдХреНрд░рд╛рдЗрдм рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред


 class Field { constructor(fieldSize) { this.size = fieldSize this.data = new Array(this.size).fill(new Array(this.size).fill(undefined)) this.subscribers = {} } _cellSubscriberId(rowI, cellI) { return `row${rowI}cell${cellI}` } cellContent(rowI, cellI) { return this.data[rowI][cellI] } setCell(rowI, cellI, newContent) { console.log('setCell') this.data = [ ...this.data.slice(0, rowI), [ ...this.data[rowI].slice(0, cellI), newContent, ...this.data[rowI].slice(cellI + 1), ], ...this.data.slice(rowI + 1), ] const cellSubscriber = this.subscribers[this._cellSubscriberId(rowI, cellI)] if (cellSubscriber) { cellSubscriber() } } map(cb) { return this.data.map(cb) } // Note that we subscribe not to updates of the whole filed, but to updates of one cell only subscribeCellUpdates(rowI, cellI, onSetCellCallback) { this.subscribers[this._cellSubscriberId(rowI, cellI)] = onSetCellCallback } } 

рдЕрдм рд╣рдо рдлреАрд▓реНрдб рдЕрдкрдбреЗрдЯ рдХреЗ рд▓рд┐рдП рд╕рджрд╕реНрдпрддрд╛ рд▓реЗ рд╕рдХрддреЗ рд╣реИрдВред


 const Cell = ({ rowI, cellI }) => { console.log('cell render') const forceRender = useForceRender() useEffect(() => field.subscribeCellUpdates(rowI, cellI, forceRender), [ forceRender, ]) const content = field.cellContent(rowI, cellI) return ( <div onClick={() => field.setCell(rowI, cellI, randomContent())}> {content} </div> ) } 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 7


рдЖрдЗрдП рдЗрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рд╛рде size рд╕рд╛рде рдЦреЗрд▓рддреЗ рд╣реИрдВред рдЗрд╕реЗ рдЙрди рдореВрд▓реНрдпреЛрдВ рдореЗрдВ рдмрдврд╝рд╛рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реЗрдВ, рдЬреЛ рдкрд╣рд▓реЗ рдкрд┐рдЫрдбрд╝ рдЧрдП рдереЗред рдФрд░ ... рдпрд╣ рд╢реИрдореНрдкреЗрди рдХреА рдПрдХ рдЕрдЪреНрдЫреА рдмреЛрддрд▓ рдЦреЛрд▓рдиреЗ рдХрд╛ рд╕рдордп рд╣реИ! рд╣рдореЗрдВ рдЦреБрдж рдПрдХ рдРрдк рдорд┐рд▓рд╛ рд╣реИ рдЬреЛ рдПрдХ рд╕реЗрд▓ рдФрд░ рдПрдХ рд╕реЗрд▓ рдХрд╛ рдкреНрд░рддрд┐рдкрд╛рджрди рдХрд░рддрд╛ рд╣реИ рдЬрдм рдЙрд╕ рд╕реЗрд▓ рдХреА рд╕реНрдерд┐рддрд┐ рдмрджрд▓ рдЬрд╛рддреА рд╣реИ!


рдЖрдЗрдП DevTools рд░рд┐рдкреЛрд░реНрдЯ рдкрд░ рдПрдХ рдирдЬрд╝рд░ рдбрд╛рд▓реЗрдВред


рд░рд┐рдПрдХреНрдЯ DevTools рд░рд┐рдкреЛрд░реНрдЯ # 4


рдЬреИрд╕рд╛ рдХрд┐ рд╣рдо рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдЕрдм рдХреЗрд╡рд▓ Cell рдХрд╛ рдкреНрд░рддрд┐рдкрд╛рджрди рдХрд┐рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ рдФрд░ рдпрд╣ рддреЗрдЬреА рд╕реЗ рдХреНрд░реЗрдЬреА рд╣реИред


рдХреНрдпрд╛ рд╣реЛрдЧрд╛ рдЕрдЧрд░ рдпрд╣ рдХрд╣рд╛ рдЬрд╛рдП рдХрд┐ рдЕрдм рд╣рдорд╛рд░реЗ Cell рдХрд╛ рдХреЛрдб рдореЗрдореЛрд░реА рд▓реАрдХ рдХрд╛ рд╕рдВрднрд╛рд╡рд┐рдд рдХрд╛рд░рдг рд╣реИ? рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдЙрдкрдпреЛрдЧ рдореЗрдВ рд╣рдо рд╕реЗрд▓ рдЕрдкрдбреЗрдЯ рдХреА рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рдо рдХрднреА рднреА рд╕рджрд╕реНрдпрддрд╛ рд╕рдорд╛рдкреНрдд рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВред рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рдЬрдм Cell рдирд╖реНрдЯ рд╣реЛ рдЬрд╛рддреА рд╣реИ, рддрдм рднреА рдЗрд╕рдХреА рд╕рджрд╕реНрдпрддрд╛ рдЪрд╛рд▓реВ рд░рд╣рддреА рд╣реИред рдЖрдЗрдП рдмрджрд▓ рджреЗрддреЗ рд╣реИрдВред


рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдореЗрдВ Field рдпрд╣ рд╕рд┐рдЦрд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдЗрд╕рдХрд╛ рд╕рджрд╕реНрдпрддрд╛ рд╕рдорд╛рдкреНрдд рдХрд░рдиреЗ рдХрд╛ рдХреНрдпрд╛ рдорддрд▓рдм рд╣реИред


 class Field { // ... unsubscribeCellUpdates(rowI, cellI) { delete this.subscribers[this._cellSubscriberId(rowI, cellI)] } } 

рдЕрдм рд╣рдо рдЕрдкрдиреЗ Cell unsubscribeCellUpdates рд▓рд╛рдЧреВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред


 const Cell = ({ rowI, cellI }) => { console.log('cell render') const forceRender = useForceRender() useEffect(() => { field.subscribeCellUpdates(rowI, cellI, forceRender) return () => field.unsubscribeCellUpdates(rowI, cellI) }, [forceRender]) const content = field.cellContent(rowI, cellI) return ( <div onClick={() => field.setCell(rowI, cellI, randomContent())}> {content} </div> ) } 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 8


рддреЛ рдпрд╣рд╛рдБ рдХреНрдпрд╛ рд╕рдмрдХ рд╣реИ? рдпрд╣ рдШрдЯрдХ рдкреЗрдбрд╝ рдХреЗ рдиреАрдЪреЗ рд░рд╛рдЬреНрдп рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрдм рд╕рдордЭ рдореЗрдВ рдЖрддрд╛ рд╣реИ? рдХрднреА рдирд╣реАрдВ! рдареАрдХ рд╣реИ, рдирд╣реАрдВ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ :) рдЬрдм рддрдХ рд╡реЗ рдЕрд╕рдлрд▓ рдирд╣реАрдВ рд╣реЛ рдЬрд╛рддреЗ рддрдм рддрдХ рд╕рд░реНрд╡реЛрддреНрддрдо рдкреНрд░рдерд╛рдУрдВ рд╕реЗ рдЪрд┐рдкрдХреЗ рд░рд╣реЗрдВ рдФрд░ рдХрд┐рд╕реА рднреА рд╕рдордп рд╕реЗ рдкрд╣рд▓реЗ рдЕрдиреБрдХреВрд▓рди рди рдХрд░реЗрдВред рдИрдорд╛рдирджрд╛рд░реА рд╕реЗ, рдЬреЛ рдорд╛рдорд▓рд╛ рд╣рдордиреЗ рдКрдкрд░ рдорд╛рдирд╛ рд╣реИ, рд╡рд╣ рдХреБрдЫ рд╣рдж рддрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ рд╣реИ, рд╣рд╛рд▓рд╛рдВрдХрд┐, рдореБрдЭреЗ рдЖрд╢рд╛ рд╣реИ рдХрд┐ рдпрджрд┐ рдЖрдк рдХрднреА рднреА рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдмрдбрд╝реА рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ рддреЛ рдЖрдк рдЗрд╕реЗ рдпрд╛рдж рдХрд░реЗрдВрдЧреЗред


рдмреЛрдирд╕ рдХрджрдо: рд╡рд╛рд╕реНрддрд╡рд┐рдХ рджреБрдирд┐рдпрд╛ рд░реАрдлреИрдХреНрдЯрд░рд┐рдВрдЧ


рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 8 рдореЗрдВ рд╣рдордиреЗ рд╡реИрд╢реНрд╡рд┐рдХ field рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛, рдЬреЛ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рджреБрдирд┐рдпрд╛ рдХреЗ рдРрдк рдореЗрдВ рдирд╣реАрдВ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдЗрд╕реЗ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЕрдкрдиреЗ App рдореЗрдВ field рдХреЛ рд╣реЛрд╕реНрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рдкреЗрдбрд╝ рдХреЗ рдиреАрдЪреЗ [рд╕рдВрджрд░реНрдн] () рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд╛рд╕ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред


 const AppContext = createContext() const App = () => { // Note how we used a factory to initialize our state here. // Field creation could be quite expensive for big fields. // So we don't want to create it each time we render and block the event loop. const [field] = useState(() => new Field(size)) return ( <AppContext.Provider value={field}> <div> {field.map((row, rowI) => ( <div key={rowI}> {row.map((cell, cellI) => ( <Cell key={`row${rowI}cell${cellI}`} rowI={rowI} cellI={cellI} /> ))} </div> ))} </div> </AppContext.Provider> ) } 

рдЕрдм рд╣рдо рдЕрдкрдиреЗ Cell рдореЗрдВ рд╕рдВрджрд░реНрдн рд╕реЗ field рдЙрдкрднреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред


 const Cell = ({ rowI, cellI }) => { console.log('cell render') const forceRender = useForceRender() const field = useContext(AppContext) useEffect(() => { field.subscribeCellUpdates(rowI, cellI, forceRender) return () => field.unsubscribeCellUpdates(rowI, cellI) }, [forceRender]) const content = field.cellContent(rowI, cellI) return ( <div onClick={() => field.setCell(rowI, cellI, randomContent())}> {content} </div> ) } 

рд▓рд╛рдЗрд╡ рдбреЗрдореЛ # 9


рдЙрдореНрдореАрдж рд╣реИ, рдЖрдкрдиреЗ рдЕрдкрдиреА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рдЙрдкрдпреЛрдЧреА рдкрд╛рдпрд╛ рд╣реИред рдЕрдкрдиреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдореБрдЭрд╕реЗ рд╕рдВрд╡рд╛рдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрд╡рддрдВрддреНрд░ рдорд╣рд╕реВрд╕ рдХрд░реЗрдВ! рдореИрдВ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдХрд┐рд╕реА рднреА рдЖрд▓реЛрдЪрдирд╛ рдФрд░ рд╕рд╡рд╛рд▓реЛрдВ рдХреА рд╕рд░рд╛рд╣рдирд╛ рдХрд░рддрд╛ рд╣реВрдВред

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


All Articles