React에서 양식을 사용한 기능적 프로그래밍

최근에 저는 React에서 몇 가지 양식을 개발하고 있었습니다. 물론 각 양식에서 입력, 유효성 검사 등을 처리하기 위해 동일한 비트의 논리를 복사하여 붙여넣는 것을 발견했기 때문에 코드 중복의 양을 줄일 수 있는 방법에 대해 생각하기 시작했습니다.

내 첫 번째 아이디어는 내 양식 구성 요소가 상속할 기본 클래스에 공유 논리를 넣는 것이었습니다. 하지만 조사해보니 React가 이런 방식으로 상속을 사용하는 것을 꺼리는 경향이 있다는 것을 알았습니다.

https://reactjs.org/docs/composition-vs-inheritance.html :

At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies.

Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions.



저는 "좋아요, 흥미롭네요. 제 형식에서 공유 논리를 추출하기 위해 구성을 어떻게 사용할 수 있을까요?"라고 생각했습니다. 몇 가지 아이디어가 있었지만 모든 것을 작동시키는 방법이 명확하지 않았습니다. 저는 약간의 조사를 했고 Formik 이라는 React용 멋진 양식 라이브러리를 살펴보았습니다.

Formik에서 양식은 기능적 구성 요소입니다. 즉, 자신의 상태를 직접 처리하지 않습니다. 대신 상태 및 일부 핸들러 함수를 매개변수로 사용하는 함수를 작성합니다. 이 함수는 전달된 매개변수에 대한 적절한 바인딩이 있는 양식에 대한 JSX를 반환합니다. 논리 및 상태 관리는 모두 각 기능 양식 구성 요소를 입력으로 사용하는 Formik 구성 요소에서 발생합니다. 나는 또한 Jared가 Formik과 같은 것을 작성하기 시작하는 방법을 보여주는 몇 가지 기본적인 스캐폴딩 코드를 설명하는 훌륭한 것을 발견했습니다.

비디오를 살펴보고 좀 더 명확하게 하기 위해 약간의 단순화를 통해 이 코드의 나만의 버전을 만들었습니다.

이 기사에서는 처음부터 Formik과 같은 것을 만드는 기본 사항을 살펴보겠습니다. 그러나 실제 애플리케이션에서 이 접근 방식을 사용하려면 실제 Formik 라이브러리를 사용하는 것이 좋습니다.

비디오와 마찬가지로 React 문서에서 basic form으로 시작합니다.



class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}


이 양식 구성 요소는 자체 상태를 관리하며 더 중요한 것은 handleInputChange 와 같은 일부 코드에 의존하며 모든 양식에 명확하게 복사하여 붙여넣을 수 있다는 것입니다.

대신, 우리의 양식을 기능적 구성 요소로 추출해 보겠습니다.

const ReservationForm = ({state, handleChange, handleBlur, handleSubmit}) => (
  <form onSubmit={handleSubmit}>
    <label>
      Is going:
      <input
        name="isGoing"
        type="checkbox"
        checked={state.values.isGoing}
        onChange={handleChange} 
        onBlur={handleBlur}/>
    </label>
    <br />
    <label>
      Number of guests:
      <input
        name="numberOfGuests"
        type="number"
        value={state.values.numberOfGuests}
        onChange={handleChange}
        onBlur={handleBlur}/>
    </label>
    <button>Submit</button>
    <pre>{JSON.stringify(state)}</pre>
  </form> 
)


저기, 더 좋아 보이지 않니? 이제 우리의 폼은 일부 매개변수를 취하고 해당 매개변수에 대한 바인딩이 포함된 JSX 조각을 반환하는 함수가 됩니다. ReservationForm 는 받은 입력에 따라 객체를 반환합니다. It's a pure function 입니다.

This functional component still has to have parameters like state, handleChange, etc. passed directly to it. That means we need to include this boilerplate as part of every form we write. The actual Formik project supplies some standard form components which allow us to bypass having to do that.



다음 질문은 "기능적 양식 구성 요소를 실제로 양식 논리를 처리하는 코드와 연결하는 방법은 무엇입니까?"입니다. 아래에서는 단순히 BabyFormikReservationForm로 감쌉니다.

const ReservationFormWithBabyFormik = props => {
  const initialValues = {
    isGoing: true,
    numberOfGuests: 2,
  }

  const onSubmit = values => alert(JSON.stringify(values))

  return <BabyFormik  initialValues={initialValues} onSubmit={onSubmit}>
    <ReservationForm/>
  </BabyFormik>
}


다음에 BabyFormikReservationForm에 액세스하는 방법을 살펴보겠습니다. 다음은 상태 관리 로직을 포함하고 전달되는 양식과 통신하는 코드입니다.

class BabyFormik extends React.Component {
  constructor(props) {
    super(props)

    this.handleChange = this.handleChange.bind(this)
    this.handleBlur = this.handleBlur.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)

    this.state = {
      values: this.props.initialValues || {},
      touched: {},
      errors: {}
    }
  }  

  handleChange(event) {    
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState(prevState => ({
      values: {
        ...prevState.values,
        [name]: value
      }
    }))
  }

  handleBlur(event) {
    const target = event.target
    const name = target.name
    this.setState(prevState => ({
      touched: {
        ...prevState.touched,
        [name]: true
      }
    }))
  }

  handleSubmit(event) {
    event.preventDefault()
    //add validation here 
    //set `isSubmitting` to true here as well
    this.props.onSubmit(this.state.values)
  }

  render() {
    //pass state and callbacks to child as parameters
    return React.cloneElement(this.props.children, {
      state: this.state,
      handleChange: this.handleChange,
      handleBlur: this.handleBlur,
      handleSubmit: this.handleSubmit
    })
  }
}

render 함수는 필요한 변수를 매개변수로 자식 구성 요소에 전달합니다. 이 예에서는 ReservationForm 입니다.

기능적 또는 객체 지향 프로그래밍과 같은 패러다임에 대한 기사는 매우 추상적이거나 너무 단순한 예제를 제공하는 경향이 있습니다. 저는 이 예제가 실용적인 맥락에서 기능적 접근 방식을 사용하는 방법을 보여주기 때문에 마음에 듭니다. 우리는 폼을 JSX를 반환하는 순수 함수로 만들고 "더러운 작업"을 상위 수준 구성 요소에 위임합니다. 이 예에서는 BabyFormik 입니다. 이것이 함수형 프로그래밍의 표준 접근 방식입니다. 가능한 한 많은 코드를 순수 함수로 작성하려고 노력하고 상태를 관리하거나 부작용을 생성하는 코드를 차단합니다.

전체 예는 다음과 같습니다.

좋은 웹페이지 즐겨찾기