3. Render Props 패턴

소개

지난 섹션에서 배운 HOC 는 컴포넌트를 함수로 감싸서 특정 기능을 부여해줬다면, 우리가 이번에 배울 Render Props 는 JSX 단에서 유사한 작업을 할 수 있게 해줍니다.

HOC 는 이런 형태로 로직을 재사용했다면:

export default withForm(
  {
    username: '',
    password: ''
  },
  true
)(LoginForm);

Render Props 는 이런 형태로 로직을 재사용합니다:

<FormManager
  initialForm={{
    username: '',
    password: ''
  }}
  onSubmit={this.handleSubmitLogin}
>
  {({ form, onChange, onSubmit }) => (
    <LoginForm onChange={onChange} onSubmit={onSubmit} form={form} />
  )}
</FormManager>

RenderProps 는 완전히 리액트스러운 로직을 위한 코드 재사용방식입니다. JSX 로 모든걸 컨트롤 할 수 있습니다. HOC와의 주요 차이점은, HOC 는 사용하기위해서 무조건 새로운 컴포넌트를 만들어야하는 방면에, Render Props 는 그렇지 않습니다. 때문에, HOC 를 사용하기 위해서 만드는 컴포넌트의 이름을 고민 할 필요 조차 없습니다.

한번 Render Props 의 기초부터 알아볼까요?

Render Prop 은 주로 두가지 형태로 사용됩니다.

첫번째 방식은 "render" 라는 이름을 가진 props 를 전달하는 것 입니다.

    <MyComponent
      render={({ name }) => (
        <div>
          Hello <b>{name}</b>!
        </div>
      )}
    />
import React, { Component } from 'react';

class MyComponent extends Component {
  render() {
    return this.props.render({ name: 'World' });
  }
}

export default MyComponent;

Edit "render" props

살짝, callback 함수의 컴포넌트 버전이라고 생각하시면 이해하기 쉽습니다. 보통 props 는 부모컴포넌트가 자식컴포넌트한테 전달해주는데, 여기선 반대로 자식 컴포넌트가 부모컴포넌트한테 특정 값을 쏴주고 있습니다.

그리고 두번째 방식은 render 라는 이름 대신에 그냥 children 자체를 함수로 전달해주는 것 입니다.

    <MyComponent>
      {({ name }) => (
        <div>
          Hello <b>{name}</b>!
        </div>
      )}
    </MyComponent>
import React, { Component } from 'react';

class MyComponent extends Component {
  render() {
    return this.props.children({ name: 'World' });
  }
}

export default MyComponent;

Edit render "children" props

이것은 그냥 형식상의 차이일뿐, 작동방식은 완전 동일합니다. render 라는 이름으로 props 를 전달할지, children 으로 넣어줄지의 문제인데, 이 두가지 방식은 개발자들의 취향에 따라 사용이 되고 있습니다.

반복되는 폼 로직 Render Props 로 구현하기

그럼 Render Props 를 사용해서 반복되는 폼 로직을 구현해보겠습니다. 여기서 사용되는 코드는 HOC 를 배울때 사용했던 코드와 완전 동일합니다.

Edit 반복되는 Form Logic

위 샌드박스에서부터 시작하겠습니다.

Render Props 를 구현 할 FormManager 컴포넌트 틀 갖추기

우리가 앞으로 만들 Render Props 컴포넌트는, FormManager 라고 이름을 지어주겠습니다. HOC는 보통 with.... 로 이름을 짓는것과 달리, Render Props 를 사용하는 컴포넌트는 기능을 명시해주는 이름으로 짓습니다. (물론 무조건 정해진 규칙은 아닙니다.)

FormManager.js 라는 컴포넌트를 만들어서 Render Props 컴포넌트의 틀을 갖춰주세요.

src/FormManager.js

import React, { Component } from 'react';

class FormManager extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  handleChange = e => {};
  handleSubmit = e => {};
  render() {
    return this.props.children({
      form: this.state,
      onChange: this.handleChange,
      onSubmit: this.handleSubmit
    });
  }
}

export default FormManager;

state 와 handleChange, handleSubmit 의 틀만 갖춰주었습니다. 그리고 이 값과 함수들은 모두, 앞으로 자신이 전달받게 될 children 함수에 파라미터로 넣어져서 호출됩니다.

폼 기능 구현하기

이어서 기능을 구현하겠습니다.

src/FormManager.js

import React, { Component } from 'react';

class FormManager extends Component {
  static defaultProps = {
    initialForm: {} // 없으면 그냥 빈 객체 사용
  };
  constructor(props) {
    super(props);
    this.state = props.initialForm;
  }
  handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };
  handleSubmit = e => {
    e.preventDefault();
    this.props.onSubmit(this.state);
    if (this.props.resetOnSubmit) {
      this.setState(this.props.initialForm);
    }
  };
  render() {
    return this.props.children({
      form: this.state,
      onChange: this.handleChange,
      onSubmit: this.handleSubmit
    });
  }
}

export default FormManager;

헷갈리는 함수가 없이 그냥 리액트 컴포넌트죠? 반복되는 로직에서 사용 할 옵션같은 것도, 모두 props 로 관리합니다.

FormManager 사용하기

한번 사용을 해볼까요? 우선 LoginForm 과 BlogPostForm 에서 state 와 커스텀 함수들을 모두 날려주고, props 에서 모든걸 받아와서 사용하는 형태로 변경을 해주겠습니다.

src/BlogPostForm.js

import React, { Component } from 'react';

export const BlogPostForm = ({ form, onChange, onSubmit }) => {
  const { title, content, tags } = form;
  return (
    <form className="BlogPostForm" onSubmit={onSubmit}>
      <input
        value={title}
        onChange={onChange}
        name="title"
        placeholder="제목"
      />
      <textarea
        value={content}
        onChange={onChange}
        name="content"
        placeholder="내용"
      />
      <input value={tags} onChange={onChange} name="tags" placeholder="태그" />
      <button>작성</button>
    </form>
  );
};

export default BlogPostForm;

src/LoginForm.js

import React, { Component } from 'react';

const LoginForm = ({ form, onChange, onSubmit }) => {
  const { username, password } = form;
  return (
    <form className="LoginForm" onSubmit={onSubmit}>
      <input
        onChange={onChange}
        value={username}
        name="username"
        placeholder="계정"
      />
      <input
        onChange={onChange}
        value={password}
        name="password"
        type="password"
        placeholder="비밀번호"
      />
      <button>로그인</button>
    </form>
  );
};

export default LoginForm;

이제, App 에서 LoginForm 과 BlogPostForm 을 각각 FormManager 로 감싸줘봅시다!

src/App.js

import React, { Component } from 'react';
import Layout from './Layout';
import BlogPostForm from './BlogPostForm';
import LoginForm from './LoginForm';
import Output from './Output';
import FormManager from './FormManager';

class App extends Component {
  state = {
    blogPost: null,
    login: null
  };

  handleSubmitLogin = login => {
    this.setState({
      login
    });
  };

  handleSubmitBlogPost = blogPost => {
    this.setState({
      blogPost
    });
  };

  render() {
    const { blogPost, login } = this.state;
    return (
      <Layout
        login={
          <FormManager
            initialForm={{
              username: '',
              password: ''
            }}
            onSubmit={this.handleSubmitLogin}
          >
            {({ form, onChange, onSubmit }) => (
              <LoginForm onChange={onChange} onSubmit={onSubmit} form={form} />
            )}
          </FormManager>
        }
        blogPost={
          <FormManager
            initialForm={{
              title: '',
              content: '',
              tags: ''
            }}
            onSubmit={this.handleSubmitBlogPost}
            resetOnSubmit={true}
          >
            {({ form, onChange, onSubmit }) => (
              <BlogPostForm
                onChange={onChange}
                onSubmit={onSubmit}
                form={form}
              />
            )}
          </FormManager>
        }
        output={<Output blogPost={blogPost} login={login} />}
      />
    );
  }
}

export default App;

Edit Render Props 로 해결

Render Props 를 사용해보니 어떤가요? 정말 리액트스럽지 않나요? 나중에 배우게 될 Context API 에서도, 이 Render Props 패턴을 사용하게 됩니다.

results matching ""

    No results matching ""