티스토리 뷰

React.JS

[React.JS] Lifting State Up

버미노트 2017. 4. 24. 16:35

React.JS

같은 데이터를 여러 컴포넌트에서 사용해야 할 때가 있습니다. 이 경우 각각의 컴포넌트가 동일한 값을 가지고 있는 방법이 있지만, 이 방법 보다도 공통 부모 컴포넌트가 진실의 근원(source of truth)이 되어 자식 컴포넌트에게 (parent -> child 방향으로) 데이터를 흘려보내는 방법이 더욱 좋습니다.

사용자의 입력값을 소문자, 대문자로 변경해 보여주는 컴포넌트를 만들어 보겠습니다.

1. Data flow (top-down data flow)

작성할 코드의 구조는 아래 그림과 같습니다.

top-down data flow
top-down data flow

부모 컴포넌트에서 자식 컴포넌트로 데이트를 흘려보내야 합니다.

function TextLength(props) {
  return <p>Text lenght : {props.text.length}</p>
}

class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text:"" };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    this.setState({ text: event.target.value });
  }

  render() {
    var text = this.state.text;
    return (
      <fieldset>
        <legend>Enter text:</legend>
        <input value={text} onChange={this.handleChange} />
        <TextLength text={text} />
      </fieldset>
    )
  }
}

ReactDOM.render(
  <TextInput />,
  document.getElementById('root')
);

CodePen으로 예제 확인하기

  • 21번 줄, input의 value에 state.text를 저장하여 input이 state.text를 보이게 됩니다. 또한 사용자가 값을 입력할 경우 handleChange가 실행되도록 이벤트 핸들러를 등록합니다.
  • 12~14번 줄, 사용자가 값을 입력할 경우 실행되는 이벤트 핸들러로, 사용자의 입력 값을 state에 저장합니다.
  • 22번 줄, 부모 컴포넌트(TextInput)가 자식 컴포넌트(TextLength)로 데이터를 흘려 보내는 부분입니다.
  • 1~3번 줄, 자식 컴포넌트(TextLenght)로 부모 컴포넌트(TextInput)에게서 받은 데이터로, 문자열의 길이를 출력합니다.

2. 두개의 Input 사용하기

이번에 추가할 코드의 구조는 아래 그림과 같습니다.

두개의 Input 사용하기
두개의 Input 사용하기

부모 컴포넌트를 만들어 두개의 자식 컴포넌트를 가지는 형태를 만들 계획입니다. 아직은 각각의 자식 컴포넌트(TextInput)가 각자 state를 가지고 있습니다.

부모 컴포넌트 만들기

class TextGroup extends React.Component {
  render() {
    return (
      <div>
        <TextInput textCase="lower" />
        <TextInput textCase="upper" />
      </div>
    );
  }
}

TextGroup로 부모 컴포넌트를 만들고, TextGroup는 TextInput 컴포넌트 두 개를 자식 컴포넌트로 둡니다.

TextInput 컴포넌트는 각각 어떠한 문자열을 출력할 것인지 textCase property로 구분하도록 구현하였습니다.

완성

function TextLength(props) {
  return <p>Text lenght : {props.text.length}</p>
}

class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text:"" };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    this.setState({ text: event.target.value });
  }

  render() {
    var textCase = this.props.textCase;
    var text = this.state.text;
    return (
      <fieldset>
        <legend>{textCase} Area</legend>
        <input value={text} onChange={this.handleChange} />
        <TextLength text={text} />
      </fieldset>
    )
  }
}

class TextGroup extends React.Component {
  render() {
    return (
      <div>
        <TextInput textCase="lower" />
        <TextInput textCase="upper" />
      </div>
    );
  }
}

ReactDOM.render(
  <TextGroup />,
  document.getElementById('root')
);

CodePen으로 예제 확인하기

3. 대문자, 소문자 변환 함수

대문자, 소문자로 변환 시키는 함수 입니다.

function toUpperText(text) {
  return text.toUpperCase();
}

function toLowerText(text) {
  return text.toLowerCase();
}

4. 공통부모로 state 올리기

다음으로 input에서 공통적으로 사용하는 state를 공통부모로 올려야 합니다.

공통부모로 state 올리기
공통부모로 state 올리기

TextGroup 컴포넌트는 진실의 근원(source of truth)가 되어 고유한 state를 가지게 됩니다.

TextGroup 컴포넌트는 TextInput 컴포넌트에서 전달 받은 text를 state에 저장하고, 대문자 또는 소문자로 변경하여 다시 TextInput 컴포넌트에게 넘겨 줍니다. 그리고, TextLength 컴포넌트에도 text를 넘겨주어 글자 길이를 출력할 수 있게 합니다.

TextInput의 state를 props로 변경하기

// Code 1
class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text:"" };
    this.handleChange = this.handleChange.bind(this);
  }
  
  handleChange(event) {
    this.setState({ text: event.target.value });
  }
  
  render() {
    var textCase = this.props.textCase;
    var text = this.state.text;

    return (
      <fieldset>
        <legend>{textCase} Area</legend>
        <input value={text} onChange={this.handleChange} />
        <TextLength text={text} />
      </fieldset>
    )
  }
}
// Code 2
class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }
  
  handleChange(event) {
    this.props.onTextChange(event.target.value);
  }
  
  render() {
    var textCase = this.props.textCase;
    var text = this.props.text;
    
    return (
      <fieldset>
        <legend>{textCase} Area</legend>
        <input value={text} onChange={this.handleChange} />
      </fieldset>
    );
  }
}
  • Code 1의 5번 줄, state를 초기화 해주는 코드를 제거하여 Code 2와 같이 만들어야 합니다.
  • Code 1의 10번 줄, TextInput 컴포넌트의 state에 저장하는 text를 부모 컴포넌트의 state에 저장해야 하기 때문에, 이벤트 핸들러에서 state를 업데이트 해주는 부분을 Code 2의 9번 줄과 같이 부모의 이벤트 핸들러를 호출하는 것으로 변경해야 합니다.
  • Code 1의 15번 줄, input에 보여줄 value를 this.state.text에서 부모 컴포넌트에서 전해 받은 this.props.text로 변경합니다.
  • Code 1의 21번 줄, 부모 컴포넌트에서 호출할 것이기 때문에 제거 하였습니다.

부모 컴포넌트에 state와 이벤트 핸들러 추가하기

// Code 1
class TextGroup extends React.Component {
  render() {
    return (
      <div>
        <TextInput textCase="lower" />
        <TextInput textCase="upper" />
      </div>
    );
  }
}
// Code 2
class TextGroup extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      textCase: "",
      text: ""
    };
    
    this.handleLower = this.handleLower.bind(this);
    this.handleUpper = this.handleUpper.bind(this);
  }
  
  handleLower(text) {
    this.setState({
      textCase: "lower",
      text: text
    });
  }
  
  handleUpper(text) {
    this.setState({
      textCase: "upper",
      text: text
    });
  }
  
  render() {
    var text = this.state.text;
    var lowerText = toLowerText(text);
    var upperText = toUpperText(text);
    
    return (
      <div>
        <TextInput textCase="lower" text={lowerText} onTextChange={this.handleLower} />
        <TextInput textCase="upper" text={upperText} onTextChange={this.handleUpper} />
        <TextLength text={text} />        
      </div>
    );
  }
}
  • Code 2의 5~8번 줄, 자식 컴포넌트에서 보이기 위해 사용되는 state를 부모 컴포넌트에서 초기화 합니다.
  • Code 2의 14~26번 줄, 자식 컴포넌트에서 호출되어 사용자가 입력한 값을 state에 저장하는 이벤트 핸들러 입니다.
  • Code 2의 30~31번 줄, 사용자가 입력한 값을 소문자와 대문자 문자열로 변경합니다.
  • Code 2의 35~36번 줄, 소문자와 대문자 문자열을 자식 컴포넌트에게 전달하고 자식 컴포넌트에서 호출될 이벤트 핸들러를 등록합니다.

완성

function toUpperText(text) {
  return text.toUpperCase();
}

function toLowerText(text) {
  return text.toLowerCase();
}

function TextLength(props) {
  return <p>Text lenght : {props.text.length}</p>
}

class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    this.props.onTextChange(event.target.value);
  }

  render() {
    var textCase = this.props.textCase;
    var text = this.props.text;

    return (
      <fieldset>
        <legend>{textCase} Area</legend>
        <input value={text} onChange={this.handleChange} />
      </fieldset>
    );
  }
}

class TextGroup extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      textCase: "",
      text: ""
    };

    this.handleLower = this.handleLower.bind(this);
    this.handleUpper = this.handleUpper.bind(this);
  }

  handleLower(text) {
    this.setState({
      textCase: "lower",
      text: text
    });
  }

  handleUpper(text) {
    this.setState({
      textCase: "upper",
      text: text
    });
  }

  render() {
    var text = this.state.text;
    var lowerText = toLowerText(text);
    var upperText = toUpperText(text);

    return (
      <div>
        <TextInput textCase="lower" text={lowerText} onTextChange={this.handleLower} />
        <TextInput textCase="upper" text={upperText} onTextChange={this.handleUpper} />
        <TextLength text={text} />        
      </div>
    );
  }
}

ReactDOM.render(
  <TextGroup />,
  document.getElementById('root')
);

CodePen으로 예제 확인하기

참고

'React.JS' 카테고리의 다른 글

[React.JS] 리액트 라우터(react-router v4)  (2) 2017.04.28
[React.JS] Composition VS Inheritance  (0) 2017.04.25
[React.JS] Forms  (0) 2017.04.22
[React.JS] List와 Key  (0) 2017.04.18
[React.JS] Handling Events  (0) 2017.04.07
댓글
공지사항
최근에 올라온 글