Tôi đã đọc một loạt react
mã và tôi thấy những thứ như thế này mà tôi không hiểu:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Tôi đã đọc một loạt react
mã và tôi thấy những thứ như thế này mà tôi không hiểu:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Câu trả lời:
Đó là một chức năng curried
Đầu tiên, kiểm tra chức năng này với hai tham số
const add = (x, y) => x + y
add(2, 3) //=> 5
Đây là một lần nữa ở dạng curried
const add = x => y => x + y
Đây là cùng 1 mã không có chức năng mũi tên
const add = function (x) {
return function (y) {
return x + y
}
}
Tập trung vào return
Nó có thể giúp hình dung nó theo một cách khác. Chúng tôi biết rằng các hàm mũi tên hoạt động như thế này - chúng ta hãy chú ý đặc biệt đến giá trị trả về .
const f = someParam => returnValue
Vì vậy, add
hàm của chúng ta trả về một hàm - chúng ta có thể sử dụng dấu ngoặc đơn để thêm rõ ràng. Các in đậm văn bản là giá trị trả về của hàm của chúng tôiadd
const add = x => (y => x + y)
Nói cách khác, add
một số số trả về một hàm
add(2) // returns (y => 2 + y)
Gọi chức năng curried
Vì vậy, để sử dụng chức năng curried của chúng tôi, chúng tôi phải gọi nó là một chút khác nhau
add(2)(3) // returns 5
Điều này là do lệnh gọi hàm đầu tiên (bên ngoài) trả về hàm thứ hai (bên trong). Chỉ sau khi chúng ta gọi hàm thứ hai, chúng ta mới thực sự nhận được kết quả. Điều này càng rõ ràng hơn nếu chúng ta tách các cuộc gọi trên hai đường dây
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3) // returns 5
Áp dụng hiểu biết mới của chúng tôi vào mã của bạn
liên quan: Có gì khác biệt giữa ràng buộc, ứng dụng một phần và cà ri?
OK, bây giờ chúng tôi hiểu cách thức hoạt động, hãy xem mã của bạn
handleChange = field => e => {
e.preventDefault()
/// Do something here
}
Chúng ta sẽ bắt đầu bằng cách biểu diễn nó mà không sử dụng các hàm mũi tên
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
};
};
Tuy nhiên, vì các chức năng mũi tên liên kết từ vựng this
, nó thực sự sẽ trông giống như thế này
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
}.bind(this)
}.bind(this)
Có lẽ bây giờ chúng ta có thể thấy những gì nó đang làm rõ ràng hơn. Các handleChange
chức năng được tạo ra một chức năng cho một quy định field
. Đây là một kỹ thuật React tiện dụng vì bạn bắt buộc phải thiết lập trình nghe của riêng mình trên mỗi đầu vào để cập nhật trạng thái ứng dụng của bạn. Bằng cách sử dụng handleChange
chức năng, chúng tôi có thể loại bỏ tất cả các mã trùng lặp sẽ dẫn đến việc thiết lập trình change
nghe cho từng trường. Mát mẻ!
1 Ở đây tôi không phải liên kết từ vựng this
vì add
hàm ban đầu không sử dụng bất kỳ ngữ cảnh nào, vì vậy việc bảo tồn nó trong trường hợp này không quan trọng.
Mũi tên nhiều hơn
Có thể sắp xếp nhiều hơn hai hàm mũi tên, nếu cần -
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
Chức năng curried có khả năng những điều đáng ngạc nhiên. Dưới đây chúng ta thấy $
được định nghĩa là một hàm được xử lý với hai tham số, nhưng tại trang web cuộc gọi, nó xuất hiện như thể chúng ta có thể cung cấp bất kỳ số lượng đối số nào. Curry là sự trừu tượng của 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
Ứng dụng một phần
Ứng dụng một phần là một khái niệm liên quan. Nó cho phép chúng ta áp dụng một phần các chức năng, tương tự như currying, ngoại trừ chức năng không phải được xác định ở dạng 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
Đây là bản demo hoạt động partial
mà bạn có thể chơi trong trình duyệt của riêng mình -
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">
$
đã được sử dụng để demo khái niệm này, nhưng bạn có thể đặt tên cho nó bất cứ điều gì bạn muốn. Trùng hợp nhưng hoàn toàn không liên quan, $
đã được sử dụng trong các thư viện phổ biến như jQuery, nơi $
là điểm nhập cảnh toàn cầu cho toàn bộ thư viện hàm. Tôi nghĩ rằng nó cũng đã được sử dụng ở những người khác. Một thứ khác bạn sẽ thấy là _
, phổ biến trong các thư viện như gạch dưới và lodash. Không một biểu tượng nào có ý nghĩa hơn biểu tượng khác; bạn gán ý nghĩa cho chương trình của bạn . Nó đơn giản là JavaScript hợp lệ: D
$
bằng cách xem cách sử dụng nó. Nếu bạn đang hỏi về việc thực hiện chính nó, $
là một hàm nhận giá trị x
và trả về một hàm mới k => ...
. Nhìn vào phần thân của hàm được trả về, chúng ta thấy k (x)
chúng ta k
cũng phải biết là một hàm và bất kể kết quả của k (x)
nó được đưa vào là gì $ (...)
, mà chúng ta biết sẽ trả về một hàm khác k => ...
, và nó sẽ đi ... Nếu bạn vẫn bị mắc kẹt, cho tôi biết.
abc(1,2,3)
là ít hơn lý tưởng hơn abc(1)(2)(3)
. Thật khó để lý luận về logic của mã và thật khó để đọc hàm abc và khó đọc lệnh gọi hàm hơn. Trước đây bạn chỉ cần biết abc làm gì, bây giờ bạn không chắc chắn những chức năng chưa được đặt tên mà abc sẽ trả về, và hai lần tại đó.
Hiểu các cú pháp có sẵn của các hàm mũi tên sẽ cho bạn hiểu về hành vi mà chúng đang giới thiệu khi 'xâu chuỗi' như trong các ví dụ bạn cung cấp.
Khi một hàm mũi tên được viết mà không có dấu ngoặc khối, có hoặc không có nhiều tham số, biểu thức cấu thành cơ thể của hàm sẽ được trả về hoàn toàn . Trong ví dụ của bạn, biểu thức đó là một hàm mũi tên khác.
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()
} | | }
} | } | }
Một lợi thế khác của việc viết các hàm ẩn danh bằng cú pháp mũi tên là chúng bị ràng buộc từ vựng theo phạm vi mà chúng được xác định. Từ 'Hàm mũi tên' trên MDN :
Một biểu hiện mũi tên chức năng có một cú pháp ngắn hơn so với biểu hiện chức năng và giải nghĩa từ vựng liên kết với các này giá trị. Các chức năng mũi tên luôn ẩn danh .
Điều này đặc biệt thích hợp trong ví dụ của bạn khi xem xét rằng nó được lấy từ một phản ứngứng dụng. Như @naomik đã chỉ ra, trong React, bạn thường truy cập các hàm thành viên của một thành phần bằng cách sử dụng this
. Ví dụ:
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) | }
Một mẹo chung, nếu bạn bị nhầm lẫn bởi bất kỳ cú pháp JS mới nào và cách nó sẽ biên dịch, bạn có thể kiểm tra babel . Ví dụ: sao chép mã của bạn trong babel và chọn cài đặt trước es2015 sẽ cho đầu ra như thế này
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
Hãy nghĩ về nó như thế này, mỗi khi bạn nhìn thấy một mũi tên, bạn thay thế nó bằng function
. function parameters
được xác định trước mũi tên.
Vì vậy, trong ví dụ của bạn:
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}
và sau đó cùng nhau:
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
.
Ngắn gọn và đơn giản
Đây là một hàm trả về một hàm khác được viết theo cách ngắn gọn.
const handleChange = field => e => {
e.preventDefault()
// Do something here
}
// is equal to
function handleChange(field) {
return function(e) {
e.preventDefault()
// Do something here
}
}
Tại sao mọi người làm điều đó ❓
Bạn đã phải đối mặt khi bạn cần phải viết một chức năng có thể được tùy chỉnh? Hoặc bạn phải viết một hàm gọi lại có các tham số (đối số) cố định, nhưng bạn cần truyền nhiều biến hơn cho hàm nhưng tránh các biến toàn cục? Nếu câu trả lời của bạn là " có " thì đó là cách làm.
Ví dụ: chúng tôi có một button
cuộc gọi lại với onClick. Và chúng ta cần truyền id
vào hàm, nhưng onClick
chỉ chấp nhận một tham số event
, chúng ta không thể truyền các tham số phụ trong phạm vi như thế này:
const handleClick = (event, id) {
event.preventDefault()
// Dispatch some delete action by passing record id
}
Nó sẽ không làm việc!
Do đó, chúng ta tạo một hàm sẽ trả về hàm khác với phạm vi biến riêng mà không có biến toàn cục, bởi vì biến toàn cục là xấu.
Bên dưới hàm handleClick(props.id)}
sẽ được gọi và trả về một hàm và nó sẽ có id
trong phạm vi của nó! Cho dù nhấn bao nhiêu lần, các id sẽ không ảnh hưởng hoặc thay đổi lẫn nhau, chúng hoàn toàn bị cô lập.
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
)
Ví dụ trong câu hỏi của bạn là câu hỏi curried function
sử dụng arrow function
và có một implicit return
đối số đầu tiên.
Hàm mũi tên liên kết từ vựng này tức là chúng không có this
đối số riêng nhưng lấy this
giá trị từ phạm vi kèm theo
Một mã tương đương ở trên sẽ là
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
Một điều nữa cần lưu ý về ví dụ của bạn là định nghĩa handleChange
là const hoặc hàm. Có lẽ bạn đang sử dụng nó như là một phần của phương thức lớp và nó sử dụng mộtclass fields syntax
vì vậy thay vì liên kết trực tiếp hàm ngoài, bạn sẽ liên kết nó trong hàm tạo của lớp
class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}
Một điều khác cần lưu ý trong ví dụ là sự khác biệt giữa lợi nhuận ngầm và rõ ràng.
const abc = (field) => field * 2;
Trên đây là một ví dụ về lợi nhuận ngầm. nó lấy trường giá trị làm đối số và trả về kết quả field*2
chỉ định rõ ràng hàm trả về
Để trả về rõ ràng, bạn sẽ nói rõ phương thức trả về giá trị
const abc = () => { return field*2; }
Một điều khác cần lưu ý về các chức năng mũi tên là chúng không có riêng arguments
nhưng cũng thừa hưởng điều đó từ phạm vi cha mẹ.
Ví dụ: nếu bạn chỉ định nghĩa một hàm mũi tên như
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
Là một hàm mũi tên thay thế cung cấp các tham số còn lại mà bạn có thể sử dụng
const handleChange = (...args) => {
console.log(args);
}
Nó có thể không hoàn toàn liên quan, nhưng vì câu hỏi được đề cập đến phản ứng sử dụng trường hợp (và tôi tiếp tục va vào luồng SO này): Có một khía cạnh quan trọng của chức năng mũi tên kép không được đề cập rõ ràng ở đây. Chỉ mũi tên 'đầu tiên' (chức năng) được đặt tên (và do đó 'có thể phân biệt' theo thời gian chạy), bất kỳ mũi tên nào sau đây đều ẩn danh và từ quan điểm React được tính là một đối tượng 'mới' trên mỗi kết xuất.
Do đó, chức năng mũi tên kép sẽ khiến bất kỳ PureComponent nào cũng phải chạy lại mọi lúc.
Thí dụ
Bạn có một thành phần cha mẹ với trình xử lý thay đổi là:
handleChange = task => event => { ... operations which uses both task and event... };
và với một kết xuất như:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
xử lýChange sau đó được sử dụng trên một đầu vào hoặc nhấp chuột. Và tất cả điều này hoạt động và trông rất đẹp. NHƯNG điều đó có nghĩa là bất kỳ thay đổi nào sẽ khiến cha mẹ đăng ký lại (như thay đổi trạng thái hoàn toàn không liên quan) cũng sẽ hiển thị lại TẤT CẢ MyTask của bạn mặc dù chúng là PureComponents.
Điều này có thể được giảm bớt bằng nhiều cách như vượt qua mũi tên 'ngoài cùng' và đối tượng bạn sẽ cung cấp cho nó hoặc viết một hàm nênUpdate tùy chỉnh hoặc quay lại các vấn đề cơ bản như viết các hàm có tên (và ràng buộc điều này bằng tay ...)