2018년 상반기 스터디 - react.js 5주

Posted by 서동우 on April 14, 2018

9장 리액트 리덕스

  • 리액트에서 상태를 관리하는 방법은 6장에서 배운 root 컨포넌트에서 상태를 설정해서 자식 컴포넌트에 프로퍼티로 전달하는 방법이다.
  • 위의 방법은 컴포넌트 트리 구조가 작은 곳에서는 효율적으로 사용할 수 있지만, 트리 구조가 커질 경우 프로그램 복잡도가 커진다.
  • 위의 방법을 개선하기 위해서 redux를 활용하는 방법이 있다.
  • redux를 활용할 수 있는 여러 방법에 대해서 살펴보고 react-redux 프레임워크를 사용하는 방법에 대해서도 알아보자.

9.1 스토어를 명시적으로 전달하기

  • 스토어를 컴포넌트 트리의 프로퍼티로 명시적으로 UI에 전달하는 방법
  • 가장 논리적이 방법
//Redux Store를 생성
const store = storeFactory();

const render = () => 
    ReactDOM.render(
        <App store={store} />,
        document.getElementById('root')
    );

store.subscribe(render);
render();


const App = ({store}) => 
    <div className="app">
        <AddColorForm store={store} />
        <ColorList store={store} />
    </div>;


const AddColorForm = ({store}) => {
    let title, color;
    
    const submit = e => {
        e.preventDefault();
        store.dispatch(addColor(title.value, color.value));
        title.value = '';
        color.value = '#000'
        title.focus();
    };

    return (
        <form onSubmit={submit}>
            <input type="text" placeholder="색 이름..." required ref={input => title = input} />
            <input type="color" required ref={input => color = input} />
            <button>추가</button>
        </form>
    );

};
  • 컴포넌트 트리가 작은 경우 아주 잘 작동
  • 스토어를 자식 컴포넌트에 명시적으로 전달해줘야 하는 단점
  • 트리가 커지면 같은 소스를 반복적으로 계속 작성해야 한다는 문제점
  • 정의한 스토어만 사용해야 하기 때문에 이 애플리케이션 말고 다른 애플리케이션에서 재활용하기 힘들다.

9.2 컨텍스트를 통해 스토어 전달하기(16.2.0 버전까지)

  • 컨텍스트라는 리액트 기능을 활용하면 컴포넌트 트리의 프로퍼를 통해 변수를 명시적으로 전달하지 않고도 값을 전달할 수 있다.
  • 모든 자식 컴포넌트를 컨텍스트를 통해 필요한 변수에 접근가능
  • getChildContext 생애주기 함수를 활용해서 컨텍스트를 생성
  • Root 컴포넌트에는 childContextTypes 를 지정하여 컨텍스트 객체를 정의해야 함. 이 단계 필수
  • 함수형 자식 컴포넌트 두 번째 인자로 전달됨
  • 클래스형 자식 컴포넌트는 this.context를 통해서 전달됨
  • 자식 컴포넌트에는 contextTypes 를 설정해야 함, 이 단계 필수
const store = storeFactory();

ReactDOM.render(
    <App store={store} />,
    document.getElementById('root')
);

class App extends React.Component {

    getChildContext() {
        return {
            store: this.props.store
        }
    }

    componentWillMount() {
        this.unsubscribe = this.props.store.subscribe(
            () => this.forceUpdate()
        );
    }

    componentWillUnmount() {
        this.unsubscribe();
    }

    render() {
        return  (
            <div className="app">
                <AddColorForm />
                <ColorList />
            </div>);

    }

    static propTypes = {
        store: PropTypes.object.isRequired
    }

    static childContextTypes = {
        store: PropTypes.object.isRequired
    }
}

const AddColorForm = (props, {store}) => {
    let title, color;
    
    const submit = e => {
        e.preventDefault();
        store.dispatch(addColor(title.value, color.value));
        title.value = '';
        color.value = '#000'
        title.focus();
    };

    return (
        <form onSubmit={submit}>
            <input type="text" placeholder="색 이름..." required ref={input => title = input} />
            <input type="color" required ref={input => color = input} />
            <button>추가</button>
        </form>
    );

};

AddColorForm.contextType = {
    store: PropTypes.object
}

9.2.1 컨텍스트를 통해 스토어 전달하기(16.3.0 버전이후)

  • 16.3.0 버전부터 컨텍스트를 사용하는 방법이 변경이 되었다.
const store = storeFactory();
const StoreContext = React.createContext(store);


ReactDOM.render(
    <StoreContext.Provider value={store}>
        <App store={store}/>
    </StoreContext.Provider>,
    document.getElementById('root')
);

class App extends React.Component {

    componentWillMount() {
        this.unsubscribe = this.props.store.subscribe(
            () => this.forceUpdate()
        );
    }

    componentWillUnmount() {
        this.unsubscribe();
    }

    render() {
        return  (
            <div className="app">
                <AddColorForm />
                <ColorList />
            </div>);

    }
}

const AddColorForm = (props}) => {
    let title, color;
    
    const submit = store => e => {
        e.preventDefault();
        store.dispatch(addColor(title.value, color.value));
        title.value = '';
        color.value = '#000'
        title.focus();
    };

    return (
            <ThemeContext.Consumer>
            store => 
                <form onSubmit={submit(store)}>
                    <input type="text" placeholder="색 이름..." required ref={input => title = input} />
                    <input type="color" required ref={input => color = input} />
                    <button>추가</button>
                </form>
            </ThemeContext.Consumer>
        );
    );

};

9.3 표현 컴포넌트와 컨테이너 컴포넌트 비교

  • 표현 컴포넌트
    1. UI 엘리먼트를 랜더링하기만 하는 컴포넌트
    2. 특정 데이터 아키텍처와는 무관하며 데이터를 프로퍼티로 받고 콜백 함수 플로퍼티를 통해 부모 컴포넌트에 데이터를 보낸다.
    3. UI 만 처리
  • 컨테이너 컴포넌트
    1. 표현 컴포넌트와 데이터를 연결해주는 컴포넌트
    2. 컨텍스트에서 스토어를 꺼내고, 스토어와의 모든 상호작용을 처리
    3. 표현 컴포넌트의 프로퍼티를 상태와 연결해주고 콜백 함수 프로퍼티를 스토어의 dispatch와 연결해줌으로써 표현 컴포넌트를 랜더링해준다.
    4. UI에는 관심없다.
  • 장점
    1. 표현 컴포넌트를 재활용
    2. 교체와 테스트가 쉽다.
  • 컨테이너와 표현 컴포넌트를 분리하는 것이 좋은 경우가 많지만 항상 그런 것은 아니기 때문에 분리해서 코드 복잡도를 개선할 수 있는 경우에만 이런 짓을 해라
class App extends React.Component {

    componentWillMount() {
        this.unsubscribe = this.props.store.subscribe(
            () => this.forceUpdate()
        );
    }

    componentWillUnmount() {
        this.unsubscribe();
    }

    render() {
        return  (
            <div className="app">
                <ThemeContext.Consumer>
                    stores => <Colors stores={stores} />
                </ThemeContext.Consumer>
            </div>);
    }
}

const Colors = props => {
    const {store } = props;
    const {colors, sort} = store;
    const sortedColors = ...;

    return (
        <ColorList colors={sortedColors} onRemove={id => store.dispatch(removeColor(id))} onRate={(id, rating) => store.dispatch(rateColor(id, rating))} />        
    );
}

9.4 리액트 리덕스 프로바이더 / connect

  • 컨텍스트를 통해 스토어를 전달하는 과정의 복잡함을 덜어주는 도구를 제공
  • 컨텍스트에 스토어를 설정할 때 사용할 수 있는 컴포넌트인 프로바이더를 제공
  • 자식 컴포넌트는 컨텍스트를 활용하여 store 정보를 가져올 수 있다.
  • connect는 컴포넌트를 반환하는 함수를 반환하는 고차 함수
  • connect를 활용하면 store 정보를 처리할 수 있는 컨테이너 컴포넌트를 쉽게 작성할 수 있다.
const store = storeFactory();

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

const App = () => 
    <div className="app">
        <AddColorForm  />
        <Colors />
    </div>;

const mapStateToProps = state => ({
    colors: [...state.colors]
});

const mapDispatchToProps = dispatch => ({
    onRemove(id) {
        dispatch(removeColor(id));
    }, onRate(id, rating) {
        dispatch(rateColor(id, rating));
    }
});

const Colors = connect(
    mapStateToProps, mapDispatchToProps
)(ColorList);

참고자료

  • 러닝 리액트
  • https://reactjs.org/docs/context.html