ฉันอ่านreact
รหัสมาแล้วและฉันเห็นสิ่งต่าง ๆ ที่ฉันไม่เข้าใจ:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
ฉันอ่านreact
รหัสมาแล้วและฉันเห็นสิ่งต่าง ๆ ที่ฉันไม่เข้าใจ:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
คำตอบ:
นั่นคือฟังก์ชั่น curried
ก่อนอื่นให้ตรวจสอบฟังก์ชันนี้ด้วยพารามิเตอร์สองตัว ...
const add = (x, y) => x + y
add(2, 3) //=> 5
ที่นี่เป็นอีกครั้งในรูปแบบ curried ...
const add = x => y => x + y
นี่คือรหัส1เดียวกันโดยไม่มีฟังก์ชั่นลูกศร ...
const add = function (x) {
return function (y) {
return x + y
}
}
เน้นไปที่ return
มันอาจช่วยให้เห็นภาพอีกวิธีหนึ่ง เรารู้ว่าฟังก์ชั่นการทำงานของลูกศรเช่นนี้ - ความสนใจโดยเฉพาะให้ของกับค่าตอบแทน
const f = someParam => returnValue
ดังนั้นadd
ฟังก์ชั่นของเราส่งคืนฟังก์ชัน - เราสามารถใช้วงเล็บเพื่อเพิ่มความชัดเจน ตัวหนาข้อความเป็นค่าตอบแทนของฟังก์ชั่นของเราadd
const add = x => (y => x + y)
ในคำอื่น ๆadd
ของบางตัวเลขส่งกลับฟังก์ชั่น
add(2) // returns (y => 2 + y)
เรียกฟังก์ชั่น curried
ดังนั้นเพื่อที่จะใช้ฟังก์ชั่น curried ของเราเราต้องเรียกมันว่าแตกต่างออกไปเล็กน้อย ...
add(2)(3) // returns 5
นี่เป็นเพราะการเรียกใช้ฟังก์ชันแรก (ด้านนอก) ส่งกลับฟังก์ชันที่สอง (ภายใน) หลังจากที่เราเรียกฟังก์ชั่นที่สองแล้วเราจะได้ผลลัพธ์ สิ่งนี้ชัดเจนมากขึ้นถ้าเราแยกการโทรออกเป็นสองบรรทัด ...
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3) // returns 5
การใช้ความเข้าใจใหม่ของเรากับรหัสของคุณ
เกี่ยวข้อง: ” อะไรคือความแตกต่างระหว่างการผูกแอปพลิเคชั่นบางส่วนและการแกง?”
ตกลงตอนนี้เราเข้าใจวิธีการทำงานแล้วลองดูรหัสของคุณ
handleChange = field => e => {
e.preventDefault()
/// Do something here
}
เราจะเริ่มต้นด้วยการแสดงมันโดยไม่ใช้ฟังก์ชั่นลูกศร ...
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
};
};
อย่างไรก็ตามเนื่องจากฟังก์ชั่นลูกศรผูกเข้าthis
ด้วยกันจริง ๆ แล้วมันจะมีลักษณะเช่นนี้ ...
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
}.bind(this)
}.bind(this)
บางทีตอนนี้เราสามารถเห็นสิ่งที่กำลังทำอยู่ชัดเจนยิ่งขึ้น ฟังก์ชั่นคือการสร้างฟังก์ชั่นสำหรับการระบุhandleChange
field
นี่เป็นเทคนิคการตอบโต้ที่สะดวกเพราะคุณต้องตั้งค่าผู้ฟังของคุณเองในแต่ละอินพุตเพื่ออัปเดตสถานะแอปพลิเคชันของคุณ โดยการใช้handleChange
ฟังก์ชั่นเราสามารถกำจัดรหัสที่ซ้ำกันทั้งหมดซึ่งจะส่งผลให้การตั้งค่าchange
ฟังสำหรับแต่ละฟิลด์ เย็น!
1 ที่นี่ฉันไม่จำเป็นต้องผูกคำศัพท์this
เพราะadd
ฟังก์ชั่นดั้งเดิมไม่ได้ใช้บริบทใด ๆ ดังนั้นจึงไม่สำคัญที่จะต้องรักษามันไว้ในกรณีนี้
ลูกศรมากยิ่งขึ้น
ฟังก์ชั่นลูกศรมากกว่าสองรายการสามารถจัดลำดับได้หากจำเป็น -
const three = a => b => c =>
a + b + c
const four = a => b => c => d =>
a + b + c + d
three (1) (2) (3) // 6
four (1) (2) (3) (4) // 10
ฟังก์ชั่น Curried มีความสามารถในสิ่งที่น่าแปลกใจ ด้านล่างเราเห็น$
ว่าฟังก์ชั่น curried มีสองพารามิเตอร์ แต่ที่ไซต์การโทรดูเหมือนว่าเราสามารถระบุอาร์กิวเมนต์จำนวนเท่าใดก็ได้ ความดีความชอบเป็นนามธรรมของarity -
const $ = x => k =>
$ (k (x))
const add = x => y =>
x + y
const mult = x => y =>
x * y
$ (1) // 1
(add (2)) // + 2 = 3
(mult (6)) // * 6 = 18
(console.log) // 18
$ (7) // 7
(add (1)) // + 1 = 8
(mult (8)) // * 8 = 64
(mult (2)) // * 2 = 128
(mult (2)) // * 2 = 256
(console.log) // 256
แอปพลิเคชั่นบางส่วน
แอปพลิเคชันบางส่วนเป็นแนวคิดที่เกี่ยวข้อง มันช่วยให้เราสามารถใช้ฟังก์ชั่นบางส่วนคล้ายกับการแกงยกเว้นฟังก์ชั่นไม่จำเป็นต้องกำหนดในรูปแบบ curried -
const partial = (f, ...a) => (...b) =>
f (...a, ...b)
const add3 = (x, y, z) =>
x + y + z
partial (add3) (1, 2, 3) // 6
partial (add3, 1) (2, 3) // 6
partial (add3, 1, 2) (3) // 6
partial (add3, 1, 2, 3) () // 6
partial (add3, 1, 1, 1, 1) (1, 1, 1, 1, 1) // 3
นี่คือตัวอย่างการทำงานที่partial
คุณสามารถเล่นกับเบราว์เซอร์ของคุณเอง -
const partial = (f, ...a) => (...b) =>
f (...a, ...b)
const preventDefault = (f, event) =>
( event .preventDefault ()
, f (event)
)
const logKeypress = event =>
console .log (event.which)
document
.querySelector ('input[name=foo]')
.addEventListener ('keydown', partial (preventDefault, logKeypress))
<input name="foo" placeholder="type here to see ascii codes" size="50">
$
ถูกใช้เพื่อสาธิตแนวคิด แต่คุณสามารถตั้งชื่อสิ่งที่คุณต้องการ บังเอิญ แต่ที่ไม่เกี่ยวข้องอย่างสมบูรณ์$
ได้ถูกนำมาใช้ในห้องสมุดได้รับความนิยมเช่น jQuery ซึ่ง$
เป็นประเภทของจุดเข้าทั่วโลกไปยังห้องสมุดทั้งหมดของฟังก์ชั่น ฉันคิดว่ามันถูกใช้ในคนอื่นด้วย อีกสิ่งที่คุณจะเห็นคือเป็นที่_
นิยมในห้องสมุดเช่นขีดล่างและ lodash ไม่มีใครมีความหมายมากกว่าสัญลักษณ์อื่น; คุณกำหนดความหมายสำหรับโปรแกรมของคุณ มันเป็นเพียง JavaScript ที่ถูกต้อง: D
$
กับการใช้งานได้ดีขึ้น หากคุณกำลังถามเกี่ยวกับการดำเนินงานของตัวเอง$
เป็นฟังก์ชั่นที่ได้รับความคุ้มค่าและผลตอบแทนฟังก์ชั่นใหม่x
k => ...
มองไปที่ร่างของฟังก์ชั่นกลับมาเราจะเห็นk (x)
เพื่อให้เรารู้k
ยังต้องฟังก์ชั่นและสิ่งผลมาจากการk (x)
ถูกนำกลับมาได้รับในการ$ (...)
ที่เรารู้ว่าผลตอบแทนอื่นk => ...
และมันจะไป ... หากคุณยังคง ติดขัดให้ฉันรู้
abc(1,2,3)
abc(1)(2)(3)
มันยากที่จะให้เหตุผลเกี่ยวกับตรรกะของรหัสและมันยากที่จะอ่านฟังก์ชั่น abc และมันยากที่จะอ่านการเรียกใช้ฟังก์ชัน ก่อนที่คุณจะต้องการเพียงรู้ว่า abc ทำอะไรตอนนี้คุณไม่แน่ใจว่าฟังก์ชัน abc ที่ไม่มีชื่อกลับมาทำอะไร
การทำความเข้าใจไวยากรณ์ที่มีอยู่ของฟังก์ชั่นลูกศรจะทำให้คุณเข้าใจถึงพฤติกรรมที่พวกเขาแนะนำเมื่อ 'ผูกมัด' เหมือนในตัวอย่างที่คุณให้ไว้
เมื่อเขียนฟังก์ชันลูกศรโดยไม่มีเครื่องหมายปีกกาบล็อกโดยมีหรือไม่มีพารามิเตอร์หลายตัวนิพจน์ที่ถือเป็นเนื้อความของฟังก์ชันจะถูกส่งกลับโดยปริยาย ในตัวอย่างของคุณนิพจน์นั้นเป็นฟังก์ชันลูกศรอื่น
No arrow funcs Implicitly return `e=>{…}` Explicitly return `e=>{…}`
---------------------------------------------------------------------------------
function (field) { | field => e => { | field => {
return function (e) { | | return e => {
e.preventDefault() | e.preventDefault() | e.preventDefault()
} | | }
} | } | }
ข้อดีอีกอย่างของการเขียนฟังก์ชั่นที่ไม่ระบุชื่อโดยใช้ไวยากรณ์ลูกศรก็คือมันจะถูกผูกเข้ากับขอบเขตที่กำหนดไว้ จาก'ฟังก์ชั่นลูกศร' บน MDN :
การแสดงออกของฟังก์ชั่นลูกศรมีไวยากรณ์ที่สั้นกว่าเมื่อเทียบกับการแสดงออกของฟังก์ชั่นและผูกค่านี้ศัพท์ ฟังก์ชั่นลูกศรอยู่เสมอที่ไม่ระบุชื่อ
นี่เป็นตัวอย่างที่เกี่ยวข้องโดยเฉพาะอย่างยิ่งเมื่อพิจารณาว่านำมาจาก reactjsใบสมัคร เท่าที่ออกมาชี้โดย @naomik ในการตอบสนองคุณมักจะเข้าถึงองค์ประกอบของฟังก์ชั่นสมาชิกthis
ใช้ ตัวอย่างเช่น:
Unbound Explicitly bound Implicitly bound
------------------------------------------------------------------------------
function (field) { | function (field) { | field => e => {
return function (e) { | return function (e) { |
this.setState(...) | this.setState(...) | this.setState(...)
} | }.bind(this) |
} | }.bind(this) | }
function
คิดว่ามันเป็นแบบนี้ทุกครั้งที่คุณเห็นลูกศรทุกท่านแทนที่ด้วย function parameters
ถูกกำหนดไว้ก่อนลูกศร
ดังนั้นในตัวอย่างของคุณ:
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}
และจากนั้นเข้าด้วยกัน:
function (field) {
return function (e) {
e.preventDefault();
};
}
// Basic syntax:
(param1, param2, paramN) => { statements }
(param1, param2, paramN) => expression
// equivalent to: => { return expression; }
// Parentheses are optional when there's only one argument:
singleParam => { statements }
singleParam => expression
this
อย่าลืมที่จะกล่าวถึงขอบเขต lexically
สั้น ๆ และเรียบง่าย 🎈
มันเป็นฟังก์ชั่นที่ส่งกลับฟังก์ชั่นอื่นที่เขียนในทางสั้น ๆ
const handleChange = field => e => {
e.preventDefault()
// Do something here
}
// is equal to
function handleChange(field) {
return function(e) {
e.preventDefault()
// Do something here
}
}
ทำไมคนทำมัน ❓
คุณเคยประสบเมื่อต้องเขียนฟังก์ชั่นที่สามารถปรับแต่งได้หรือไม่? หรือคุณต้องเขียนฟังก์ชั่นการโทรกลับซึ่งมีพารามิเตอร์คงที่ (อาร์กิวเมนต์) แต่คุณต้องส่งตัวแปรเพิ่มเติมไปยังฟังก์ชัน แต่หลีกเลี่ยงตัวแปรทั่วโลก หากคำตอบของคุณคือ " ใช่ " นั่นเป็นวิธีที่จะทำ
ตัวอย่างเช่นเรามีbutton
with onClick callback และเราจำเป็นต้องส่งผ่านid
ไปยังฟังก์ชั่น แต่onClick
ยอมรับเพียงหนึ่งพารามิเตอร์event
เท่านั้นเราไม่สามารถส่งพารามิเตอร์เพิ่มเติมภายในดังนี้:
const handleClick = (event, id) {
event.preventDefault()
// Dispatch some delete action by passing record id
}
มันจะไม่ทำงาน!
ดังนั้นเราจึงสร้างฟังก์ชั่นที่จะคืนค่าฟังก์ชันอื่นด้วยขอบเขตของตัวแปรที่ไม่มีตัวแปรทั่วโลกเพราะตัวแปรทั่วโลกเป็น evil ที่ชั่วร้าย
ฟังก์ชั่นด้านล่างhandleClick(props.id)}
จะถูกเรียกและส่งคืนฟังก์ชั่นและมันจะมีid
อยู่ในขอบเขต! ไม่ว่าจะกดกี่ครั้งรหัสจะไม่ส่งผลหรือเปลี่ยนแปลงซึ่งกันและกัน
const handleClick = id => event {
event.preventDefault()
// Dispatch some delete action by passing record id
}
const Confirm = props => (
<div>
<h1>Are you sure to delete?</h1>
<button onClick={handleClick(props.id)}>
Delete
</button>
</div
)
ตัวอย่างในคำถามของคุณคือตัวอย่างcurried function
ที่ใช้arrow function
และมีimplicit return
อาร์กิวเมนต์แรก
ฟังก์ชั่นลูกศรผูกอย่างนี้คือพวกเขาไม่มีthis
ข้อโต้แย้งของตัวเองแต่รับthis
ค่าจากขอบเขตการปิดล้อม
เทียบเท่ากับรหัสข้างต้นจะเป็น
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
อีกสิ่งหนึ่งที่ควรทราบเกี่ยวกับตัวอย่างของคุณคือการกำหนดhandleChange
เป็น const หรือฟังก์ชัน อาจเป็นเพราะคุณใช้มันเป็นส่วนหนึ่งของวิธีการเรียนและใช้class fields syntax
ดังนั้นแทนที่จะผูกฟังก์ชั่นด้านนอกโดยตรงคุณจะผูกมันไว้ในตัวสร้างคลาส
class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}
อีกสิ่งที่ควรทราบในตัวอย่างคือความแตกต่างระหว่างผลตอบแทนโดยนัยและชัดเจน
const abc = (field) => field * 2;
ด้านบนเป็นตัวอย่างของการส่งคืนโดยนัยคือ ใช้ฟิลด์ค่าเป็นอาร์กิวเมนต์และส่งคืนผลลัพธ์field*2
ซึ่งระบุฟังก์ชันที่จะส่งคืนอย่างชัดเจน
สำหรับการส่งคืนอย่างชัดเจนคุณจะบอกวิธีการคืนค่าอย่างชัดเจน
const abc = () => { return field*2; }
สิ่งที่ควรทราบเกี่ยวกับฟังก์ชั่นลูกศรก็คือพวกเขาไม่มีของตัวเองarguments
แต่สืบทอดมาจากขอบเขตผู้ปกครองเช่นกัน
ตัวอย่างเช่นหากคุณเพิ่งกำหนดฟังก์ชั่นลูกศรเช่น
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
ในฐานะที่เป็นฟังก์ชั่นลูกศรทางเลือกให้พารามิเตอร์ที่เหลือที่คุณสามารถใช้
const handleChange = (...args) => {
console.log(args);
}
มันอาจจะไม่เกี่ยวข้องกันโดยสิ้นเชิง แต่เนื่องจากคำถามที่กล่าวถึงปฏิกิริยาใช้ case (และฉันเก็บไว้ในเธรด SO นี้): มีสิ่งสำคัญอย่างหนึ่งของฟังก์ชั่น double arrow ซึ่งไม่ได้กล่าวถึงอย่างชัดเจนที่นี่ เฉพาะลูกศร 'แรก' (ฟังก์ชัน) เท่านั้นที่ได้รับการตั้งชื่อ (และทำให้ 'แยกแยะได้' ในขณะใช้งาน) ลูกศรต่อไปนี้ไม่ระบุชื่อและจากมุมมองการตอบสนองนับเป็นวัตถุ 'ใหม่' ในทุกการแสดงผล
ดังนั้นฟังก์ชั่นลูกศรคู่จะทำให้ PureComponent ใด ๆ แสดงผลตลอดเวลา
ตัวอย่าง
คุณมีองค์ประกอบหลักที่มีตัวจัดการการเปลี่ยนแปลงดังนี้:
handleChange = task => event => { ... operations which uses both task and event... };
และด้วยการแสดงผลเช่น:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
handleChange จากนั้นใช้กับอินพุตหรือคลิก ทั้งหมดนี้ใช้งานได้และดูดีมาก แต่หมายความว่าการเปลี่ยนแปลงใด ๆ ที่จะทำให้ผู้ปกครองแสดงผล (เช่นการเปลี่ยนแปลงสถานะที่ไม่เกี่ยวข้องอย่างสมบูรณ์) จะทำให้ MyTask ของคุณแสดงผลทั้งหมดอีกครั้งแม้ว่าพวกเขาจะเป็น PureComponents ก็ตาม
สิ่งนี้สามารถบรรเทาได้หลายวิธีเช่นการส่งลูกศร 'นอกสุด' และวัตถุที่คุณจะป้อนหรือเขียนฟังก์ชัน shouldUpdate ที่กำหนดเองหรือกลับไปสู่พื้นฐานเช่นการเขียนฟังก์ชันที่มีชื่อ (และผูกสิ่งนี้ด้วยตนเอง ... )