Hooks: Why Function Components?

GAEEEGURI·2023년 5월 21일
1

이 글은 React에서 왜 함수형 컴포넌트(Hooks)가 사용되기 시작했는지에 관해 정리한 글입니다.


Hooks is new way to handle state and lifecycle.

Previous way: class component

class Counter extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0
		};
	}

	render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

So, What is the problem?

Classes

  • template for creating objects
  • built on prototypes but also have some syntax and semantics that are unique to classes
  • just GRAMMAR (ES6)

Syntactic sugar to hide Constructor / Prototype pattern

→ JavaScript is still prototype based language

ℹ️ There is no difference on how those code work.

function Monster(name) {
	this.name = name;
}

Monster.prototype.attack
	= function () {
		console.log('attack!');
	}

const monsterRat 
	= new Monster('rat');

monsterRat.attack();
// attack!
class Monster {
	constructor(name) {
		this.name = name;
	}
	attack() {
		console.log('attack!');
	}
}

const monsterRat
	= new Monster('rat');

monsterRat.attack();
// attack!

this in JavaScript

  • this, aka “the context”, is a special keyword inside each function and its value only depends on how the function was called, not how/when/where it was defined.
  • It is not affected by lexical scopes like other variables
function foo() { console.log(this); }

// normal function call
foo(); // `this` will refer to `window`

// as object method
var obj = {bar: foo}
obj.bar(); // `this` will refer to `obj`

// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`

How to refer to the correct this

  • Use arrow functions (lambda functions)
    • don’t have their own this binding
    • this is looked up in scope just like a normal variable = don’t have to call bind
      function MyConstructor(data, transport) {
      	this.data = data;
      	tansport.on('data', () => alert(this.data));
      }
  • Explicitly set this of the callback
    • Every function has the method [.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) , which returns a new function with this bound to a value

    • this will always refer to the passed value

      function MyConstructor(data, transport) {
      	this.data = data;
      	var boundFunction = (function() { 
      		alert(this.data); 
      	}).bind(this); // call `.bind()`
      	transport.on('data', boundFunction);
      }
      		
  • Don’t use this
    • Why do we use this? We want to access the object it refers to
    • Create new variable that also refers to that object
      function MyConstructor(data, transport) {
      	this.data = data;
      	var self = this;
      	transport.on('data', function() {
      		alert(self.data);
      	});
      }

Example of problem

function Foo() {
	this.data = 42;
	document.body.onclick = this.method;
}

Foo.prototype.method = function() {
	console.log(this.data);
};

The function this.method is assigned as click event handler, but if the document.body is clicked, the value logged will be undefined, because inside the event handler, this refers to the document.body, not the instance of Foo .

If you want to specify this, you can do one of following.

document.body.conclick = this.method.bind(this);
// or
document.body.onclick = () => this.method();

Again,

What this refers to depends on how the function is called, not how it is defined.

In React

  • Case 1
class MyComponent extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 0
		}
		// bind method
		// this.handleClick = this.handleClick.bind(this);
	}
	// error
	handleClick() {
		this.setState({count: this.state.count + 1});
	}
	
	render() {
		return (
			<div>
				<h4>{this.state.count}</h4>
				<button type='button' onClick={this.handleClick}>
					Click Me!
				</button>
			</div>
		)
	}
}
  • Case 2

pjqnl16lm7 - CodeSandbox

Props are immutable in React so they can never change. However, this is, and has always been, mutable.

Problem of Class Components

  • It’s hard to reuse stateful logic between components 
    Generally when you use HOC or renderProps you have to restructure your App with multiple hierarchies when you try to see it in DevTools, Hooks avoid such scenarios and help in clearer code
  • Complex components become hard to understand 
    Often with classes Mutually unrelated code often ends up together or related code tends to be split apart, it becomes more and more difficult to maintain. An example of such a case is event listeners, where you add listeners in componentDidMount and remove them in componentWillUnmount . Hooks let you combine these two
  • Classes confuse both people and machines 
    With classes you need to understand binding and the context in which functions are called, which often becomes confusion.

Function Components & Hooks

Function components capture the rendered values

React passes them as an argument. Unlike this, the props object itself is never mutated by React.

  • What if we want to read the latest props or state that don’t belong to this particular render? What if we want to “read them from the future”?
  • You can also have a mutable value that is shared by all component renders. It’s called a “ref”
    function MyComponent() {
      const ref = React.useRef(null);
      // You can read or write `ref.current`.
      // ...
    }

Hooks

  • State Management: In class components, state was managed using the this.state object and the this.setState() method. With hooks, functional components can use the useState hook to define and update state. The useState hook returns an array with the current state value and a function to update it. This simplifies the syntax and eliminates the need for class-based state management.

    function MyComponent() {
    	const [state, setState] = React.useState(/*initial value*/);
    	// ...
    }
  • Lifecycle and Side Effects: Class components had lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount for handling side effects and component lifecycle events. With hooks, functional components can use the useEffect hook to manage side effects and mimic component lifecycle. The useEffect hook allows you to perform actions when the component mounts, updates, or unmounts. It replaces multiple lifecycle methods with a single hook, resulting in more concise and focused code.

    componentDidMount() { ... }
    useEffect(() => { ... }, [])
    
    componentWillUnmount() { ... }
    useEffect(() => { return () => { ... } }, [])
    
    componentDidUpdate() { ... }
    useEffect(() => { ... })
  • Performance Optimization: Hooks provide optimizations such as memoization and fine-grained control over component updates. Hooks like useMemo and useCallback allow you to memoize expensive computations and prevent unnecessary re-renders. This can lead to better performance and more efficient rendering compared to class components.

    Premature optimizations are the root of all evil - worry about this when it's a problem.

References

Questions I asked ChatGPT while writing this😜

  • what is the benefit of using class components in React?
  • why does Flutter use components based on class?
  • Why does React recommend using functional components instead of class?
  • how does react team optimized functional components with hooks? how does it work?
  • What is react state?
  • what is lifecycle of component in react?
  • what is design pattern?
  • how do hooks replace class components in react?
  • what is the mental model of useEffect in react?
  • is this in javascript dynamic value?
  • Why do I have to bind custom method in react class component?
profile
개구리가멍멍

0개의 댓글