Mengapa Anda tidak boleh menggunakan fungsi panah sebaris di alat peraga JSX
Menggunakan fungsi panah atau pengikatan di JSX adalah praktik buruk yang merusak performa, karena fungsi tersebut dibuat ulang pada setiap render.
Setiap kali fungsi dibuat, fungsi sebelumnya adalah sampah yang dikumpulkan. Merender ulang banyak elemen dapat membuat animasi jank.
Menggunakan fungsi panah sebaris akan menyebabkan PureComponent
s, dan komponen yang digunakan shallowCompare
dalam shouldComponentUpdate
metode tetap merender. Karena prop fungsi panah dibuat ulang setiap kali, perbandingan dangkal akan mengidentifikasinya sebagai perubahan pada prop, dan komponen akan merender ulang.
Seperti yang Anda lihat pada 2 contoh berikut - ketika kita menggunakan fungsi panah sebaris, <Button>
komponen dirender setiap kali (konsol menampilkan teks 'tombol render').
Contoh 1 - PureComponent tanpa handler inline
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Contoh 2 - PureComponent dengan handler inline
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Mengikat metode ke this
tanpa fungsi panah sebaris
Mengikat metode secara manual di konstruktor:
class Button extends React.Component {
constructor(props, context) {
super(props, context);
this.cb = this.cb.bind(this);
}
cb() {
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Mengikat metode menggunakan proposal-class-fields dengan fungsi panah. Karena ini adalah proposal tahap 3, Anda perlu menambahkan preset Tahap 3 atau properti Kelas diubah ke konfigurasi babel Anda.
class Button extends React.Component {
cb = () => { // the class property is initialized with an arrow function that binds this to the class
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Komponen Fungsi dengan panggilan balik batin
Saat kita membuat fungsi bagian dalam (penanganan peristiwa misalnya) di dalam komponen fungsi, fungsi tersebut akan dibuat ulang setiap kali komponen dirender. Jika fungsi diteruskan sebagai props (atau melalui konteks) ke komponen anak ( Button
dalam hal ini), anak itu juga akan dirender ulang.
Contoh 1 - Komponen Fungsi dengan callback batin:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Untuk mengatasi masalah ini, kita bisa membungkus callback dengan useCallback()
hook , dan mengatur dependensi ke array kosong.
Catatan: yang useState
fungsi yang dihasilkan menerima fungsi updater, yang menyediakan kondisi saat ini. Dengan cara ini, kita tidak perlu menyetel ketergantungan pada status saat ini useCallback
.
Contoh 2 - Komponen Fungsi dengan callback dalam yang dibungkus dengan useCallback:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
this
, jadi tidak ada yang perlu diikat. Biasanya metode ini disediakan oleh komponen pintar pembungkus.onClick={() => { onTodoClick(todo.id) }
cb() { onTodoClick(this.props.todo.id); }
.useCallback
dengan nilai dinamis. stackoverflow.com/questions/55006061/…Ini karena fungsi panah tampaknya akan membuat instance baru dari fungsi tersebut pada setiap render jika digunakan dalam properti JSX. Hal ini dapat membuat beban berat pada pengumpul sampah dan juga akan menghalangi browser untuk mengoptimalkan "jalur panas" karena fungsi akan dibuang alih-alih digunakan kembali.
Anda dapat melihat penjelasan lengkapnya dan beberapa info lebih lanjut di https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
sumber
bind
atau fungsi panah di sini adalah menembak diri Anda sendiri di kaki. Ini tidak didokumentasikan dengan baik, terutama dalam kasus bekerja denganmap
larik ping dalam Daftar, dll.Untuk menghindari pembuatan fungsi baru dengan argumen yang sama, Anda dapat membuat memo hasil fungsi bind, berikut adalah utilitas sederhana bernama
memobind
untuk melakukannya: https://github.com/supnate/memobindsumber
Menggunakan fungsi sebaris seperti ini tidak masalah. Aturan linting sudah usang.
Aturan ini berasal dari saat fungsi panah tidak umum dan orang menggunakan .bind (this), yang dulu lambat. Masalah kinerja telah diperbaiki di Chrome 49.
Perhatikan bahwa Anda tidak meneruskan fungsi inline sebagai props ke komponen anak.
Ryan Florence, penulis React Router, telah menulis banyak hal tentang ini:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
sumber
Anda dapat menggunakan fungsi panah menggunakan pustaka react-cached-handler , tidak perlu khawatir tentang kinerja rendering ulang:
Fitur lainnya:
sumber
Mengapa alat peraga JSX tidak menggunakan fungsi panah atau mengikat?
Sebagian besar, karena fungsi inline dapat merusak memoisasi komponen yang dioptimalkan:
Ini lebih sedikit tentang biaya pembuatan fungsi tambahan:
Kapan
react/jsx-no-bind
aturan itu berguna?Anda ingin memastikan, bahwa komponen yang dimoisasi berfungsi sebagaimana mestinya:
React.memo
(untuk komponen fungsi)PureComponent
atau customshouldComponentUpdate
(untuk komponen kelas)Dengan mematuhi aturan ini, referensi objek fungsi stabil akan diteruskan. Jadi komponen di atas dapat mengoptimalkan kinerja dengan mencegah render ulang, bila alat peraga sebelumnya tidak berubah.
Bagaimana cara mengatasi kesalahan ESLint?
Kelas: Definisikan penangan sebagai metode, atau properti kelas untuk
this
pengikatan.Hooks: Gunakan
useCallback
.Middleground
Dalam banyak kasus, fungsi inline sangat nyaman digunakan dan sangat baik dalam hal persyaratan kinerja. Sayangnya, aturan ini tidak dapat dibatasi hanya untuk jenis komponen yang dimo. Jika Anda masih ingin menggunakannya secara menyeluruh, Anda dapat menonaktifkannya misalnya untuk simpul DOM sederhana:
sumber