2019: ลอง hooks + contract debouncing
นี่เป็นเวอร์ชันล่าสุดของวิธีแก้ไขปัญหานี้ ฉันจะใช้:
นี่คือการเดินสายเริ่มต้นบางส่วน แต่คุณกำลังเขียนบล็อกดั้งเดิมด้วยตัวคุณเองและคุณสามารถสร้างเบ็ดแบบกำหนดเองของคุณเองเพื่อที่คุณจะต้องทำสิ่งนี้เพียงครั้งเดียว
// Generic reusable hook
const useDebouncedSearch = (searchFunction) => {
// Handle the input text state
const [inputText, setInputText] = useState('');
// Debounce the original search async function
const debouncedSearchFunction = useConstant(() =>
AwesomeDebouncePromise(searchFunction, 300)
);
// The async callback is run each time the text changes,
// but as the search function is debounced, it does not
// fire a new request on each keystroke
const searchResults = useAsync(
async () => {
if (inputText.length === 0) {
return [];
} else {
return debouncedSearchFunction(inputText);
}
},
[debouncedSearchFunction, inputText]
);
// Return everything needed for the hook consumer
return {
inputText,
setInputText,
searchResults,
};
};
จากนั้นคุณสามารถใช้ตะขอของคุณ:
const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))
const SearchStarwarsHeroExample = () => {
const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
return (
<div>
<input value={inputText} onChange={e => setInputText(e.target.value)} />
<div>
{searchResults.loading && <div>...</div>}
{searchResults.error && <div>Error: {search.error.message}</div>}
{searchResults.result && (
<div>
<div>Results: {search.result.length}</div>
<ul>
{searchResults.result.map(hero => (
<li key={hero.name}>{hero.name}</li>
))}
</ul>
</div>
)}
</div>
</div>
);
};
คุณจะพบตัวอย่างนี้ทำงานที่นี่และคุณควรอ่านเอกสารตอบกลับแบบ async-hookสำหรับรายละเอียดเพิ่มเติม
2018: ลองสัญญาว่าจะเปิดตัว
บ่อยครั้งที่เราต้องการเรียกคืนการเรียก API เพื่อหลีกเลี่ยงแบ็กเอนด์ที่มีคำขอที่ไร้ประโยชน์
ในปีพ. ศ. 2561 การทำงานกับการโทรกลับ (Lodash / Underscore) รู้สึกแย่และมีข้อผิดพลาดเกิดขึ้นกับฉัน มันเป็นเรื่องง่ายที่จะพบปัญหาสำเร็จรูปและการทำงานพร้อมกันเนื่องจากการเรียก API การแก้ไขตามลำดับโดยพลการ
ฉันได้สร้างห้องสมุดเล็ก ๆ โดยมีปฏิกิริยาตอบสนองเพื่อแก้ไขความเจ็บปวดของคุณ: คำสัญญาที่ยอดเยี่ยม
สิ่งนี้ไม่ควรซับซ้อนกว่านั้น:
const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));
const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);
class SearchInputAndResults extends React.Component {
state = {
text: '',
results: null,
};
handleTextChange = async text => {
this.setState({ text, results: null });
const result = await searchAPIDebounced(text);
this.setState({ result });
};
}
ฟังก์ชั่น debounce ทำให้มั่นใจได้ว่า:
- การเรียก API จะถูกหักล้าง
- ฟังก์ชั่น debounce มักจะส่งกลับสัญญา
- เฉพาะสัญญาที่ส่งคืนล่าสุดของสายที่จะแก้ไข
- เพียงหนึ่งเดียว
this.setState({ result });
ที่จะเกิดขึ้นต่อการโทร API
ในที่สุดคุณอาจเพิ่มเคล็ดลับอื่นถ้าองค์ประกอบของคุณ unmount:
componentWillUnmount() {
this.setState = () => {};
}
โปรดสังเกตว่าObservables (RxJS) สามารถเป็นตัวเลือกที่ยอดเยี่ยมสำหรับการเปิดใช้งานอินพุต แต่มันเป็นนามธรรมที่ทรงพลังยิ่งกว่าซึ่งอาจยากต่อการเรียนรู้ / ใช้อย่างถูกต้อง
<2017: ยังต้องการใช้การเรียกคืนการโทรกลับหรือไม่
ส่วนที่สำคัญที่นี่คือการสร้างเดียว debounced (หรือผ่อนคันเร่ง) ฟังก์ชั่นต่อเช่นองค์ประกอบ คุณไม่ต้องการสร้างฟังก์ชัน debounce (หรือ throttle) ใหม่ทุกครั้งและคุณไม่ต้องการให้หลาย ๆ อินสแตนซ์แบ่งปันฟังก์ชัน debounce เดียวกัน
ฉันไม่ได้นิยามฟังก์ชัน debouncing ในคำตอบนี้เนื่องจากมันไม่เกี่ยวข้องกันจริงๆ แต่คำตอบนี้จะทำงานได้อย่างสมบูรณ์แบบด้วย_.debounce
การขีดเส้นใต้หรือ lodash รวมถึงฟังก์ชัน debouncing ที่ผู้ใช้จัดหา
ความคิดที่ดี:
เนื่องจากฟังก์ชั่นที่หักล้างนั้นเป็นของรัฐเราจึงต้องสร้างฟังก์ชั่นหักล้างหนึ่งรายการต่ออินสแตนซ์ขององค์ประกอบต่อหนึ่งเช่นองค์ประกอบ
ES6 (คุณสมบัติคลาส) : แนะนำ
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (ตัวสร้างคลาส)
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.method = debounce(this.method.bind(this),1000);
}
method() { ... }
}
ES5
var SearchBox = React.createClass({
method: function() {...},
componentWillMount: function() {
this.method = debounce(this.method.bind(this),100);
},
});
ดูJsFiddle : 3 อินสแตนซ์กำลังสร้างรายการบันทึก 1 รายการต่ออินสแตนซ์ (ซึ่งสร้าง 3 รายการทั่วโลก)
ไม่ใช่ความคิดที่ดี:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
มันจะไม่ทำงานเพราะในระหว่างการสร้างวัตถุคำอธิบายคลาสthis
ไม่ใช่วัตถุที่สร้างขึ้นเอง this.method
ไม่ส่งคืนสิ่งที่คุณคาดหวังเนื่องจากthis
บริบทไม่ใช่วัตถุเอง (ซึ่งจริง ๆ แล้วไม่มีจริง ๆ แล้วยัง BTW ตามที่เพิ่งถูกสร้างขึ้น)
ไม่ใช่ความคิดที่ดี:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
คราวนี้คุณจะมีประสิทธิภาพการสร้างฟังก์ชั่นที่เรียก debounced this.method
ของคุณ ปัญหาคือคุณกำลังสร้างมันขึ้นมาใหม่ทุกครั้งที่debouncedMethod
ฟังก์ชั่น debounce ที่สร้างขึ้นใหม่ไม่ทราบอะไรเกี่ยวกับการโทรในอดีต! คุณต้องนำฟังก์ชั่น debounce เดิมมาใช้ใหม่ตลอดเวลามิเช่นนั้นการเรียกคืนจะไม่เกิดขึ้น
ไม่ใช่ความคิดที่ดี:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
นี่มันยุ่งยากเล็กน้อยที่นี่
อินสแตนซ์ที่เมาท์ทั้งหมดของคลาสจะใช้ฟังก์ชัน debounce เดียวกันและส่วนใหญ่มักจะไม่ใช่สิ่งที่คุณต้องการ! ดูJsFiddle : 3 อินสแตนซ์กำลังสร้างรายการบันทึกเพียงรายการเดียวทั่วโลก
คุณต้องสร้างฟังก์ชั่นการลบล้างสำหรับแต่ละอินสแตนซ์ขององค์ประกอบและไม่ใช่ฟังก์ชั่นการหักครั้งเดียวในระดับชั้นซึ่งแบ่งใช้โดยแต่ละอินสแตนซ์ขององค์ประกอบ
ดูแลการรวมเหตุการณ์ของ React
สิ่งนี้เกี่ยวข้องกันเพราะเรามักจะต้องการ debounce หรือเค้นเหตุการณ์ DOM
ในการตอบสนองวัตถุเหตุการณ์ (เช่นSyntheticEvent
) ที่คุณได้รับในการโทรกลับจะรวมกัน (ตอนนี้ได้รับการบันทึกแล้ว) ซึ่งหมายความว่าหลังจากเรียกการเรียกเหตุการณ์แล้ว SyntheticEvent ที่คุณได้รับจะถูกนำกลับไปไว้ในกลุ่มที่มีแอตทริบิวต์ว่างเพื่อลดแรงกดดัน GC
ดังนั้นหากคุณเข้าถึงSyntheticEvent
คุณสมบัติแบบอะซิงโครนัสกับการติดต่อกลับเดิม (เช่นในกรณีที่คุณเค้น / debounce) คุณสมบัติที่คุณเข้าถึงอาจถูกลบ หากคุณต้องการให้เหตุการณ์ไม่เคยถูกนำกลับมาใส่ในกลุ่มคุณสามารถใช้persist()
วิธีการ
ไม่มีพฤติกรรม (พฤติกรรมเริ่มต้น: เหตุการณ์รวม)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
ตัวที่ 2 (async) จะทำการพิมพ์hasNativeEvent=false
เนื่องจากคุณสมบัติของเหตุการณ์ถูกลบทิ้ง
ด้วยความคงอยู่
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
ตัวที่ 2 (async) จะถูกพิมพ์hasNativeEvent=true
เพราะpersist
ช่วยให้คุณหลีกเลี่ยงการใส่เหตุการณ์กลับเข้าไปในกลุ่ม
คุณสามารถทดสอบ 2 พฤติกรรมเหล่านี้ได้ที่นี่: JsFiddle
อ่านคำตอบของ Julenสำหรับตัวอย่างการใช้persist()
กับฟังก์ชั่น throttle / debounce
debounce
แต่ฉันคิดว่าคุณใช้ในทางที่ผิดของ ที่นี่เมื่อใดonChange={debounce(this.handleOnChange, 200)}/>
มันจะเรียกใช้debounce function
ทุกครั้ง แต่ที่จริงแล้วสิ่งที่เราต้องการคือเรียกใช้ฟังก์ชันที่ฟังก์ชัน debounce ส่งคืน