DEV Community

Manuel Artero Anguita 🟨
Manuel Artero Anguita 🟨

Posted on

Why I Prefer 'function' Declarations for Top-Level Symbols (But Won't Use Them Anymore)

Today, we’ve decided to use arrow functions exclusively at work.

We have a common ESLint config, and the team voted to unify this rule across all projects.

And honestly I'm not a fan of this particular rule

Personally... function declarations feels more expressive, at least for top-level symbols:

some-screen-of-my-app.tsx

import {} ...

export function SomeScreen(props: Props) {
  const { myContext } = useMyContext()
  const [state, setState] = useState()

  const doSomething = () => { ... }
  const handleSomething = () => { ... }

  return <>...</>
 }

function SomeInternalComponent() { ... }
Enter fullscreen mode Exit fullscreen mode

This is how i'm used to write components: declaring a function feels like a chapter title in a novel.

function Chapter3(storySoFar: Props) {
   // where the Hero meets the Villain
}
Enter fullscreen mode Exit fullscreen mode

But I do understand the team need: depending on the original author of a module we might find at first level const () => {} or function.

The main argument is that "arrow functions are more readable" (which i disagree with)

import {} ...

const SomeInternalComponent = () => { ... }

export const SomeScreen = (props: Props) => {
  const { myContext } = useMyContext()
  const [state, setState] = useState()

  const doSomething = () => { ... }
  const handleSomething = () => { ... }

  return <>...</>
 }
Enter fullscreen mode Exit fullscreen mode

I tried to find some technical advantage to support my preference... some nerd *pitimini* [ something small or insignificant ] that moves the balance on my benefit but since we all agree on the following:

  • No Classes (just functions)
  • No global stuff (modern modules)
  • No this

There are no significant differences between each one.

Diving into Details:

const foo = () => { ... }

  • No hoisting
  • function's name is derived from the variable's name ("foo")
  • Can't be overwritten later like foo=...
  • Doesn't create the prototype object foo.prototype
  • Can't be used as a constructor new foo()
  • Doesn't have arguments
  • this value is defined by where the function is declared

function foo() { ... }

  • Hoisting
  • function name is obv.
  • Can be overwritten like foo = ...
  • Creates the object prototype foo.prototype
  • new is allowed like: new foo() (which would link the prototype)
  • this value is defined by how the function is called

In the end, I prefer the Superior Clarity of function for top-level components, but the will of the many prevails.
Kidding, I will adapt. Having a unified style will help to maintain a cohesive codebase.

😭😭😭.


thanks for reading

Top comments (3)

Collapse
 
nausaf profile image
nausaf • Edited

very interesting post!

function hoisting is a trivial matter as unlike var hoisting, here we don't have to worry about an unintended value propagating through the code at run time (as can easily happen with var hoisting when the hoisted var gets set to undefined at start of function). A hoisted function identifier points to the function itself (rather, to the closure of the function) from the very beginning.

Given that you sensibly ban this and classes in your code, (and so this binding shouldn't matter), I think arrow functions have a slight edge as no prototype is created (none is needed) and new is not allowed (which reinforces the convention of not creating classes).

Also, I think it's nice that arrow function's variable cannot be overwritten like function can if we always using const when defining arrow functions.

Not having arguments is perhaps not a big deal. With rest parameters (...params) available, I have never quite needed to use arguments.

Not having an obvious name is a bit of a red herring for JS functions I think: when you export or import them, you can given them any name. Within a file, the variable to which the function is assigned can be reasonably seen as the name of the function..

For obvious reasons arrow functions much better for being returned from other functions as not complication of prototype or this binding.

Therefore I think overall arrow function much better.

Sadly I am too attached to function and still use them. Your post is forcing me to reevaluate my inertia. Maybe from tomorow, I will only ever use arrow functions. Thanks for pointing out this can be reinforced through ESLint.

Collapse
 
manuartero profile image
Manuel Artero Anguita 🟨

Thanks for commenting @nausaf !!

Collapse
 
nausaf profile image
nausaf

Always a pleasure reading your posts Manuel!