Eu tenho lido um monte de react
código e vejo coisas assim que não entendo:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Eu tenho lido um monte de react
código e vejo coisas assim que não entendo:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Respostas:
Essa é uma função ao curry
Primeiro, examine esta função com dois parâmetros…
const add = (x, y) => x + y
add(2, 3) //=> 5
Aqui está novamente em forma de curry…
const add = x => y => x + y
Aqui está o mesmo código 1 sem funções de seta…
const add = function (x) {
return function (y) {
return x + y
}
}
Focar em return
Pode ajudar a visualizá-lo de outra maneira. Sabemos que as funções de seta funcionam assim - vamos prestar especial atenção ao valor de retorno .
const f = someParam => returnValue
Portanto, nossa add
função retorna uma função - podemos usar parênteses para maior clareza. O texto em negrito é o valor de retorno da nossa funçãoadd
const add = x => (y => x + y)
Em outras palavras, add
de algum número retorna uma função
add(2) // returns (y => 2 + y)
Chamando funções com caril
Então, para usar nossa função ao curry, precisamos chamá-la de um jeito diferente…
add(2)(3) // returns 5
Isso ocorre porque a primeira chamada de função (externa) retorna uma segunda função (interna). Somente depois que chamamos a segunda função é que obtemos o resultado. Isso é mais evidente se separarmos as chamadas em duas linhas ...
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3) // returns 5
Aplicando nosso novo entendimento ao seu código
relacionados: "Qual é a diferença entre encadernação, aplicação parcial e curry?"
OK, agora que entendemos como isso funciona, vejamos seu código
handleChange = field => e => {
e.preventDefault()
/// Do something here
}
Começaremos por representá-lo sem usar as funções de seta ...
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
};
};
No entanto, porque as funções de seta lexically ligamento this
, seria realmente se parecem mais com isso ...
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
}.bind(this)
}.bind(this)
Talvez agora possamos ver o que isso está fazendo com mais clareza. A handleChange
função está criando uma função para um especificado field
. Esta é uma técnica útil do React, pois você precisa configurar seus próprios ouvintes em cada entrada para atualizar o estado dos aplicativos. Usando a handleChange
função, podemos eliminar todo o código duplicado que resultaria na configuração de change
listeners para cada campo. Legal!
1 Aqui não tive que ligar lexicamente this
porque a add
função original não usa nenhum contexto; portanto, não é importante preservá-la neste caso.
Ainda mais flechas
Mais de duas funções de seta podem ser sequenciadas, se necessário -
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
Funções curry são capazes de coisas surpreendentes. Abaixo, vemos $
definida como uma função ao curry com dois parâmetros, mas no site da chamada, parece que podemos fornecer qualquer número de argumentos. Currying é a abstração da aridade -
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
Aplicação parcial
Aplicação parcial é um conceito relacionado. Ele nos permite aplicar parcialmente funções, semelhantes ao curry, exceto que a função não precisa ser definida na forma de caril -
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
Aqui está uma demonstração funcional sobre a qual partial
você pode jogar em seu próprio navegador -
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">
$
foi usado para demonstrar o conceito, mas você pode chamá- lo como quiser. Coincidentemente, mas completamente independente, $
tem sido usado em bibliotecas populares como o jQuery, onde $
é uma espécie de ponto de entrada global para toda a biblioteca de funções. Eu acho que tem sido usado em outros também. Outro que você verá é _
popularizado em bibliotecas como sublinhado e lodash. Nenhum símbolo é mais significativo que outro; você atribui o significado ao seu programa. É simplesmente válido JavaScript: D
$
olhando para como é usado. Se você está perguntando sobre a implementação em si, $
é uma função que recebe um valor x
e retorna uma nova função k => ...
. Olhando para o corpo da função retornada, vemos k (x)
que k
também sabemos que deve ser uma função, e qualquer que seja o resultado k (x)
é devolvido ao $ (...)
que sabemos que devolve outra k => ...
, e continua ... Se você ainda está ficar preso, me avise.
abc(1,2,3)
é menos do que o ideal abc(1)(2)(3)
. É mais difícil argumentar sobre a lógica do código, é difícil ler a função abc e é mais difícil ler a chamada de função. Antes que você só precisasse saber o que o abc faz, agora você não tem certeza do que as funções não nomeadas que o abc está retornando fazem e duas vezes.
Compreender as sintaxes disponíveis das funções de seta fornecerá uma compreensão de qual comportamento elas estão apresentando quando encadeadas, como nos exemplos que você forneceu.
Quando uma função de seta é gravada sem chaves, com ou sem vários parâmetros, a expressão que constitui o corpo da função é retornada implicitamente . No seu exemplo, essa expressão é outra função de seta.
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()
} | | }
} | } | }
Outra vantagem de escrever funções anônimas usando a sintaxe da seta é que elas estão ligadas lexicamente ao escopo em que são definidas. Nas 'Funções da seta' no MDN :
Uma expressão de função de seta possui uma sintaxe mais curta em comparação com expressões de função e vincula lexicamente o valor this . As funções de seta são sempre anônimas .
Isso é particularmente pertinente no seu exemplo, considerando que é retirado de um reactjsinscrição. Como apontado por @naomik, no React você costuma acessar as funções de membro de um componente usando this
. Por exemplo:
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) | }
Uma dica geral, se você ficar confuso com alguma sintaxe JS nova e como ela será compilada, verifique babel . Por exemplo, copiar seu código em babel e selecionar a predefinição es2015 fornecerá uma saída como esta
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
Pense assim, toda vez que vir uma seta, substitua-a por function
. function parameters
são definidos antes da seta.
Então, no seu exemplo:
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}
e depois juntos:
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
.
Breve e simples 🎈
É uma função que retorna outra função escrita de maneira abreviada.
const handleChange = field => e => {
e.preventDefault()
// Do something here
}
// is equal to
function handleChange(field) {
return function(e) {
e.preventDefault()
// Do something here
}
}
Por que as pessoas fazem isso ?
Você já enfrentou quando precisa escrever uma função que pode ser personalizada? Ou você precisa escrever uma função de retorno de chamada que tenha parâmetros fixos (argumentos), mas precisa passar mais variáveis para a função, evitando variáveis globais? Se sua resposta for " sim ", é assim que se faz.
Por exemplo, temos um button
retorno de chamada onClick. E precisamos passar id
para a função, mas onClick
aceita apenas um parâmetro event
, não podemos passar parâmetros extras dentro desta:
const handleClick = (event, id) {
event.preventDefault()
// Dispatch some delete action by passing record id
}
Isso não vai funcionar!
Portanto, criamos uma função que retornará outra função com seu próprio escopo de variáveis sem nenhuma variável global, porque as variáveis globais são más 😈.
Abaixo a função handleClick(props.id)}
será chamada e retornará uma função e ela terá id
em seu escopo! Não importa quantas vezes seja pressionado, os IDs não se afetam ou se alteram, eles são totalmente isolados.
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
)
O exemplo na sua pergunta é o de um curried function
que faz uso arrow function
e possui um implicit return
para o primeiro argumento.
A função Arrow lexicamente liga isso, ou seja, eles não têm seu próprio this
argumento, mas recebem o this
valor do escopo
Um equivalente do código acima seria
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
Mais uma coisa a observar sobre o seu exemplo é que define handleChange
como uma const ou uma função. Provavelmente você o está usando como parte de um método de classe e ele usa umclass fields syntax
então, em vez de vincular a função externa diretamente, você a vincularia no construtor da classe
class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}
Outra coisa a observar no exemplo é a diferença entre retorno implícito e explícito.
const abc = (field) => field * 2;
Acima está um exemplo de retorno implícito, ie. pega o campo de valor como argumento e retorna o resultadofield*2
que especifica explicitamente a função a ser retornada
Para um retorno explícito, você explicitamente diria ao método para retornar o valor
const abc = () => { return field*2; }
Outra coisa a ser observada sobre as funções das setas é que elas não têm suas próprias arguments
mas herdam isso também do escopo dos pais.
Por exemplo, se você apenas definir uma função de seta como
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
Como uma seta alternativa, as funções fornecem os demais parâmetros que você pode usar
const handleChange = (...args) => {
console.log(args);
}
Pode não estar totalmente relacionado, mas como a pergunta mencionada reage ao caso de uso (e eu continuo colidindo com esse segmento SO): Há um aspecto importante da função de seta dupla que não é explicitamente mencionado aqui. Somente a 'primeira' seta (função) é nomeada (e, portanto, 'distinguível' pelo tempo de execução); quaisquer setas a seguir são anônimas e, do ponto de vista do React, são contadas como um 'novo' objeto em cada renderização.
Portanto, a função de seta dupla fará com que qualquer PureComponent seja renderizado o tempo todo.
Exemplo
Você tem um componente pai com um manipulador de alterações como:
handleChange = task => event => { ... operations which uses both task and event... };
e com uma renderização como:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
handleChange então usado em uma entrada ou clique. E tudo isso funciona e parece muito bom. MAS significa que qualquer alteração que faça com que o pai seja renderizado novamente (como uma mudança de estado completamente não relacionada) também renderizará TODAS o seu MyTask, mesmo que sejam PureComponents.
Isso pode ser aliviado de várias maneiras, como passar a seta 'mais distante' e o objeto com o qual você a alimentaria ou escrever uma função shouldUpdate personalizada ou voltar ao básico, como escrever funções nomeadas (e vincular isso manualmente ...)