-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prefer arrow functions #23
Comments
I'm not sure I understand your first argument against arrow functions. What do you mean with they don't provide arguments? Generally I like arrow functions, for me the shortness and the auto-binded this context are really good benefits which make them way more convenient to use in the majority of use cases than normal functions. |
@bluebyt I think @jhnns meant the Array-like > const blub = () => console.log(arguments)
undefined
> blub("foo", "bar")
ReferenceError: arguments is not defined
at blub (repl:1:32)
> function blub2() { console.log(arguments) }
undefined
> blub2("foo", "bar")
{ '0': 'foo', '1': 'bar' } |
I think it can sometimes lead to a visual mess, e.g. if an arrow function returns an arrow function. This: export const createGetRecipeTileImage = (images, imagesHighDpi) => (
recipeImage,
h,
w
) => { ... } is much less readable than this: // note that it is immediately clear that this is a factory
// because you can read "return function" inside the code
export function createGetRecipeTileImage(images, imagesHighDpi) {
return function (recipeImage, h, w) { ... }
} However, it only seems to be bothering me at SmartFood :) |
My thoughts:
|
@leomelzer got it. But do you often have use cases for this? The nested arrow function confusion comes from using anonymous functions, but this can be reduced if the functions are saved with meaningful names, can't it? export const createGetRecipeTileImage = (images, imagesHighDpi) => {
const someNiceName = (recipeImage, h, w) => { ... }
return someNiceName This is clearer in both cases and a preferable way, isn't it? |
I agree with you that your example is hard to read. But I don't think that it's a problem of the arrow function itself, more a problem how prettier + ESLint decide how this should be formatted. For instance, you could format it like that: export const createGetRecipeTileImage =
(images, imagesHighDpi) =>
(recipeImage, h, w) => {
...
} which I think is even more readable than the @el-moalo-loco
I know what you mean, but the distinction is hard to define and probably impossible to describe as a linting rule. It would probably be: If the function returns something, it's probably a shorter arrow function, if the function has side-effects, it's probably longer and a regular But on the other hand, it's also strange to see both function styles scattered across the code base. I'd prefer a unified function style. My assumption is also that you become used to scanning for the |
I've added them as custom style (not mandatory). We can try it out in a real-world project. |
With the popularity of TypeScript this is becoming more important. I would at least change our function style from declaration to expression: // current
function myFunction() {}
// new
const myFunction = function () {};
// or more concise
const myFunction = () => {}; The reason is TypeScript's contextual typing: // children and the return type is correctly inferred
const MyComponent: React.SFC = ({children}) => {
/* ... */
}; or // req, res, next can be inferred
const expressMiddleware: Middleware = (req, res, next) => {
}; I would definitely like to change that rule for our TypeScript linting rules. But since it is awkward to use different styles in TS and non-TS projects, I would also like to change our base rules. Since this rule is not autofixable, we can leave the old way as accepted "style". You would just have to extend What do you think @leomelzer @bytetyde @acidicX @el-moalo-loco @meaku @mksony? |
Agree, I think with the rise of typescript function expressions are a good choice, also +1 from my side for the consistency with the The only thing which I would consider a downside is that examples for generics or type guards in the typescript docs actually use function declarations https://www.typescriptlang.org/docs/handbook/advanced-types.html. |
I recently discovered another downside of function expressions in combination with module exports. There are certain situations where you might run into a runtime error during application startup. The main ingredients are:
An example: // a.js
import "./b.js"; // depend on b.js for whatever reason
export const onClick = () => {}; // b.js
import {onClick} from "./a.js"; // this creates the circular dependency
// Now let's execute some code during module evaluation
document.body.addEventListener("click", onClick); With a function expression, We ran into a similar problem at one of our projects recently. In our example, we had a circular dependency and we created Redux selectors using export const getEditingMode = createSelector(
getState,
documentStore.isLatestVersion,
documentStore.isDraftVersion,
documentStore.isLocked,
// ...
); In certain situations, Personally, I think these problems are solvable. Circular dependencies can usually be avoided by refactoring modules. E.g. you can easily put the problematic code into a dedicated module which both modules can depend on. In fact, this is usually the better choice anyway. However, I think this is the biggest downside of using function expressions I encountered so far. |
Related discussion at the TypeScript repository: microsoft/TypeScript#16918 Would be nice to get contextual typing with function declarations. |
I just ran into a problem with the If we use it with generics, we have to extend the generic or use multiple type parameters to avoid conflicts with JSX. // has conflicts
const myFn = <T>(value: T): T => value;
// works
const myFn = <T extends any>(value: T): T => value; This is especially bad if we really want to allow any value, since we do not allow const isDefined = <T extends any>(value: T): NonNullable<T> => value != null; More info: microsoft/TypeScript#4922 |
Oh shit... that sucks :(. Strange that I haven't encountered this limitation so far. I still think that the benefits of function expressions outweigh the drawbacks, but that's the biggest blocker so far. What solution would you recommend @hpohlmeyer? |
The best fix for this is to use a dangling comma in the type parameter list: const myFn = <T,>(value: T): T => value; Not beautiful, but at least it doesn't require you to use I still think that function expressions are better (at least in combination with TypeScript because of contextual typing). What do you think? |
Oh that is a nice workaround! It does not look too bad imo. |
Over the past years, my personal preference on functions vs. arrow functions has shifted towards arrow functions. I used to be against arrow functions as a general replacement as they behave different than regular
function
. Typical arguments against arrow functions in general were:arguments
this
is derived from the upper scopenew
prototype
anonymous function
in stack traces (.name
wasundefined
)But now I've changed my mind:
With rest parameters, arrow functions can have
arguments
. Rest parameters are better because they are actual arrays. And in general, I think that all thesearguments
and rest parameters tricks can be replaced with regular array arguments which are more explicit and expressive anyway.I never use
this
in regular functions. Functions that usethis
should be part of aclass
where you can use method shorthands without thefunction
keyword. A function that usesthis
and that is not part of aclass
should rather accept an explicit argument than an implicitthis
(we already enforce this withno-invalid-this
).This is a good feature. If a
function
should be callable withnew
, it should be aclass
Same as above
This is never a real issue. If it is, it can be replaced with simpler and more explicit code.Not true. This may become an issue when evaluating modules that are part of a cyclic dependency graph.This is not true anymore. JavaScript engines derive the function name from the variable name:
Benefits of preferring arrow functions:
new
(already mentioned)this
(already mentioned)return
) which are very common in functional programming patterns like Redux' action creators (see also Allow one line arrow functions #22)The two last arguments are the most important ones for me that motivated me to change the rule. What do you think about it?
The text was updated successfully, but these errors were encountered: