React 서버 렌 더 링 의 길 04 - redux - 01

모든 소스 코드, 문서, 그림 은 github 창고 에 있 습 니 다. 클릭 하여 창고 에 들 어 갑 니 다.
관련 읽 기
  • React 서버 렌 더 링 의 길 01 - 프로젝트 기초 구조 구축
  • React 서버 렌 더 링 의 길 02 - 가장 간단 한 서버 렌 더 링
  • React 서버 렌 더 링 의 길 03 - 경로
  • React 서버 렌 더 링 의 길 04 - redux - 01
  • React 서버 렌 더 링 의 길 05 - redux - 02
  • React 서버 렌 더 링 의 길 06 - 최적화
  • React 서버 렌 더 링 의 길 07 - CSS 스타일 추가
  • React 서버 렌 더 링 의 길 08 - 404 와 리 셋
  • React 서버 렌 더 링 의 길 09 - SEO 최적화
  • 1. redux
  • 경로 가 완 성 된 후에 우 리 는 데 이 터 를 고려 해 야 한다. 우 리 는 가장 광범 위 한 redux 를 사용 하여 react 의 데이터 상 태 를 관리 해 야 한다
  • .
  • 업데이트 store 는 세 가지 가 있 습 니 다.
  • 동기 화, 이 동기 화 는 클 라 이언 트 와 서버 의 통일 업데이트
  • 를 포함한다.
  • 클 라 이언 트 비동기 입 니 다. 이것 은 바로 평소에 우리 가 자주 사용 하 는 클 라 이언 트 가 요청 을 보 내 고 비동기 로 데 이 터 를 얻 은 다음 에 store 의 값 을 수정 하 는 것 입 니 다
  • .
  • 서버 의 비동기, 이것 은 비교적 복잡 합 니 다. 다음 절 에 소개 하 겠 습 니 다
  • 그래서 이 절 은 store 와 클 라 이언 트 의 비동기 업데이트 store
  • 를 소개 합 니 다.
    소개
  • store 의 생 성 은 두 가지 로 나 뉘 는데 하 나 는 클 라 이언 트 이 고 다른 하 나 는 서버 이 며 모든 엔 드 의 store 는 분리 되 어야 한다. 하나의 방법 으로 호출 하 는 것 이다. 이렇게 하 는 목적 은 클 라 이언 트 라면 모든 사용자 가 하나의 클 라 이언 트 를 가지 고 자신의 store 안의 데 이 터 를 사용 하 는 것 이다. 그러나 서버 는 다르다. 아무리 많은 클 라 이언 트 가 있 더 라 도서버 는 하나 밖 에 없 기 때문에 모든 사용자 의 store 데이터 가 혼 란 스 럽 지 않도록 서버 의 store 를 하나의 방법 으로 호출 합 니 다. 그러면 모든 사용자 가 서버 store 를 호출 할 때 자신의 방법 이 있 습 니 다. 자신의 데 이 터 를 호출 하면 데이터 가 혼 란 스 럽 지 않 습 니 다
  • 클 라 이언 트 가 store 를 사용 하 는 방법 은 평소 클 라 이언 트 렌 더 링 과 같 고 차이 가 없다
  • 서버 에서 store 를 사용 하 는 방법 도 static Router 밖 에 Provider 를 감 싸 서 서버 의 store 에 전송 하면 됩 니 다
  • 1.2 redux 가 사용 할 라 이브 러 리
  • npm i redux react-redux redux-thunk redux-logger -S
  • npm i redux-devtools-extension -D
  • redux, 이것 이 바로 redux 의 핵심 라 이브 러 리
  • react - redux 는 react 와 redux 는 전혀 관계 가 없 기 때문에 서로 독립 적 으로 사용 할 수 있 고 react 에 redux 를 직접 도입 할 수 있 지만 사용 하기 가 귀 찮 고 불편 합 니 다.그래서 우 리 는 react 와 redux 사이 에 관 계 를 맺 는 것 을 편리 하 게 하기 위해 react - redux
  • 를 사용 합 니 다.
  • redux - thunk 은 redux 가 dispatch 에 있 을 때 하나의 방법 을 사용 하도록 합 니 다. 여기 서 우리 가 사용 하 는 방법 은 주로 비동기 로 데 이 터 를 얻 기 위 한 것 입 니 다
  • redux - logger, 콘 솔 에 state 변화 기록 표시
  • redux - devtools - extension, 이것 은 구 글 브 라 우 저의 redux 플러그 인 입 니 다. 이 플러그 인 은 미들웨어 로 열 어야 state 의 변화 상 태 를 볼 수 있 습 니 다
  • 2. 가장 쉬 운 redux 사용
  • 코드 가 비교적 간단 하기 때문에 평소에 우리 클 라 이언 트 가 redux 를 사용 하 는 것 과 차이 가 적 기 때문에 코드 를 직접 봐 도 알 수 있 습 니 다
  • 여기 기능 이 세 가지 가 있어 요.
  • 첫 번 째 는 redux 의 데 이 터 를 직접 가 져 와 username 의 값 을 가 져 오 는 것 입 니 다
  • .
  • 두 번 째 는 데 이 터 를 얻 은 후에 단 추 를 누 르 면 user 의 age 값 을 수정 할 수 있 습 니 다
  • .
  • 세 번 째 는 클 라 이언 트 가 제3자 인 터 페 이 스 를 호출 하여 데 이 터 를 얻 고 user 의 schoolList 값 을 수정 하 는 것 이다
  • .

    2.1 store 만 들 기
  • store/index.js
  • import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import logger from 'redux-logger';
    import { composeWithDevTools } from 'redux-devtools-extension';
    
    import reducers from './reducers';
    
    export const getServerStore = () => createStore(
      reducers,
      composeWithDevTools(applyMiddleware(thunk, logger))
    );
    
    export const getClientStore = () => createStore(
      reducers,
      composeWithDevTools(applyMiddleware(thunk, logger))
    );
  • store/reducers.js
  • import { combineReducers } from 'redux';
    
    import userReducer from './user/reducer';
    
    export default combineReducers({
      user: userReducer
    });
  • store/user/actionTypes.js
  • export const SET_INCREMENT_AGE = 'SET_INCREMENT_AGE';
    
    export const GET_SCHOOL_LIST = 'GET_SCHOOL_LIST';
  • store/user/createActions.js
  • action 의 값 에 대해 어떤 사람 은 payload 를 사용 하 는 것 을 좋아 하고 어떤 사람 은 필요 한 값 의 변수 이름 을 직접 사용 하 는 것 을 좋아 합 니 다. 이것 은 무엇이든 사용 할 수 있 습 니 다. 앞 뒤 가 통일 되면 되 고 강제 적 인 규범 이 없습니다.
  • redux - logger 에서 payload 를 사용 하기 때문에 payload
  • 를 사용 하 는 것 을 권장 합 니 다.
  • 이것 은 실제 적 으로 하나의 속성 값 입 니 다. actions 에서 정의 한 것 과 reducer 에서 얻 은 것 이 같은 것 만 확보 하면 됩 니 다
  • 주의: 이곳 의 인 터 페 이 스 는 아 날로 그 인터페이스 입 니 다. 바로 아래 의 2.4 개의 인터페이스 서비스 입 니 다. 인 터 페 이 스 를 간단하게 정의 할 수 있 습 니 다. 그 목적 은 ajax 요청 응답 데이터
  • 를 만 들 기 위해 서 입 니 다.
    import * as Types from './actionTypes';
    import axios from 'axios';
    
    export const incrementAge = () => {
      return {
        type: Types.SET_INCREMENT_AGE
      }
    };
    
    export const getSchoolList = () => {
      return (dispatch) => {
        return axios.get('http://localhost:8758/api/getSchoolList').then(res => {
          if (res.status === 200) {
            let schoolList = res.data.schoolList;
            console.log(res.data);
            dispatch({
              type: Types.GET_SCHOOL_LIST,
              payload: schoolList
            });
          }
        });
      }
    }
  • store/user/reducer.js
  • import * as Types from './actionTypes';
    
    const initState = {
      name: 'mark',
      age: 18,
      schoolList: []
    };
    
    export default (state = initState, action) => {
      switch (action.type) {
        case Types.SET_INCREMENT_AGE:
          return { ...state, age: state.age + 1 };
        case Types.GET_SCHOOL_LIST:
          console.log(action);
          return { ...state, schoolList: action.payload };
        default:
          return { ...state };
      }
    }

    2.2 루트 파일 의 수정
  • 경로 파일 을 수정 하려 는 이 유 는 여기 서 수정 하 는 지 여 부 는 아무런 영향 이 없 지만 다음 소절 에서 도 수정 해 야 한다. 그리고 이 소절 도 비교적 간단 하기 때문에 바로 여기에 놓 고 수정 하여 다음 절의 내용 과 혼 란 스 럽 지 않도록 한다
  • .
  • 전에 우리 의 길 은 이렇게 썼 다
  • export default (
      <>
        
        
      >
    );
  • 현재 우 리 는 배열 대상 의 형식 으로 바 뀌 었 다. 왜냐하면 이것 은 우리 가 구성 요소 에서 비동기 데이터 로드
  • 를 편리 하 게 할 수 있 기 때문이다.
    export default [
      {
        path: '/',
        component: Home,
        exact: true,
        key: '/'
      },
      {
        path: '/news',
        component: News,
        exact: true,
        key: '/news'
      }
    ];
  • 그 다음 에 우 리 는 클 라 이언 트 와 서버 에서 순환 적 으로 옮 겨 다 니 고 다시 조립 하여 Route 의 형식 으로 바 꾸 었 다. 이 두 가지 방법 을 자세히 보면 차이 가 없다. 바로 한 가지 형식 을 바 꾸 었 을 뿐이다. 뒤에서 우리 가 사용 하 는 것 을 편리 하 게 하기 위해
  • {
      routes.map(route => )
    }

    2.2 클 라 이언 트 의 redux
  • client/index.js
  • import { Provider } from 'react-redux';
    import { Route } from 'react-router-dom';
    import { getClientStore } from "../store";
    
    hydrate(
      
        
          <>
            
    { routes.map(route => ) }
    > , window.root);
    • containers/Home/index.js

      • 关于 react-redux 的 connect 的用法,可以把 connect 作为组件的装饰器使用,也可以作为函数直接调用使用,因为装饰器实际上就是函数多次调用的语法糖,所以我统一把 connect 的写成函数调用的形式
      • connect 的参数,可以直接把方法写在参数里,也可以像这里一样,把 mapStateToProps 和 mapDispatchToProps 先定义成方法,然后直接把方法作为参数
      • 关于 actions 里方法的调用,我这里采用的方法,其实是有些复杂的,最简单其实就是直接在组件内部调用 actions 里的方法。我在这里又在组件内部定义了一个方法 A ,在这个组件的 props 里又定义了一个方法 B ,假如 actions 里的方法是 C 。那么最简单的方法就是直接调用 this.props.C(),但是我这里的顺序是这样的,先调用 A(),然后 A() 调用 B(),最后在 B() 里调用 C()。具体如何调用呢,根据个人喜好选择。
      • 这里呢,倒不是我鸡贼,总是说怎么用都行,实际上这个也没有什么标准写法。我还是蛮喜欢现在这种写法的,比较清晰明了,传递参数和调用什么的,都很方便,缺点就是代码量多,修改的时候,改动多
    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as UserActions from '../../store/user/createActions';
    
    class Home extends Component {
    
      state = {
        number: 0
      };
    
      handleClick = () => {
        this.setState({
          number: this.state.number + 1
        });
      };
    
      incrementAge = () => {
        this.props.propIncrementAge();
      };
    
      getSchoolList = () => {
        this.props.propGetSchoolList();
      }
    
      render() {
        return (
          

    HELLO, HOME PAGE

         {this.state.number}

    • name: {this.props.user.name}
    •      {this.props.user.age}

      { this.props.user.schoolList.map(school => (
    • {school.id}. {school.name}
    • )) }
    ); } } const mapStateToProps = state => ({ user: state.user }); const mapDispatchToProps = dispatch => ({ propIncrementAge() { dispatch(UserActions.incrementAge()); }, propGetSchoolList() { dispatch(UserActions.getSchoolList()); } }) export default connect(mapStateToProps, mapDispatchToProps)(Home);

    2.3 서버 의 redux
  • 서버 의 redux 는 동기 화 된 상태 에서 쓰기 가 비교적 간단 하고 복잡 한 것 이 없다. 사실은 store 를 Provider 에 게 직접 전달 하면 된다
  • .
  • server/index.js
  • import { Provider } from 'react-redux';
    import { Route } from 'react-router-dom';
    
    let domContent = renderToString(
      
        
          <>
            
    { routes.map(route => ) }
    > );

    2.4 接口服务

    • /server/app.js,这里已经完全放开了跨域,暂不处理,后期要做修改调整
    const express = require('express');
    
    let app = express();
    const PORT = 8758;
    
    app.use((req, res, next) => {
      res.header("Access-Control-Allow-Origin", "*");
      res.header("Access-Control-Allow-Headers", "content-type");
      res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
      next();
    });
    
    app.get('/api/getSchoolList', (req, res) => {
      let schoolList = [
        { id: 1, name: '    ' },
        { id: 2, name: '    ' },
        { id: 3, name: '    ' },
        { id: 4, name: '    ' }
      ]
      return res.json({ schoolList });
    });
    
    app.listen(PORT, err => {
      if (err) {
        console.log(err);
      } else {
        console.log(`the server is running at http://localhost:${PORT}`);
      }
    });

    2.4 총화
  • 전체적으로 보면 동기 화 된 redux 와 클 라 이언 트 의 비동기 적 인 데 이 터 를 얻 고 사용 하면 실제 적 으로 일반 클 라 이언 트 가 렌 더 링 할 때 큰 차이 가 없 기 때문에 비교적 간단 하 다
  • .
  • 복잡 한 것 은 서버 에서 비동기 로 얻 는 것 입 니 다. 여기 서 구성 요소 와 관련 된 방법, promise 의 포장, 탈수 와 물 주입 등 이 있 습 니 다. 우 리 는 다음 절 에 통일 적 으로 소개 합 니 다
  • .
    3. server / index. js 의 코드 분할
  • 뒤에 서버 / index. js 코드 를 여러 번 수정 해 야 하기 때문에 먼저 코드 를 나 누고 render. js 파일 을 나 누 어 렌 더 링 을 하 는 데 사 용 됩 니 다. index. js 파일 은 단독 서비스
  • 만 합 니 다.
  • /server/index.js
  • import express from 'express';
    import render from './render';
    
    const app = express();
    const PORT = 3000;
    
    app.use(express.static('public'));
    
    app.get('*', (req, res) => {
      render(req, res);
    });
    
    app.listen(PORT, err => {
      if (err) {
        console.log(err);
      } else {
        console.log(`Server is running at http://localhost:${PORT}`);
      }
    });
  • /server/render.js
  • import React from 'react';
    import { renderToString } from 'react-dom/server';
    import { StaticRouter, Route, matchPath } from 'react-router-dom';
    import { Provider } from 'react-redux';
    import { getServerStore } from '../store';
    
    import Header from './../components/Header/index';
    import routes from '../routes';
    
    export default (req, res) => {
    
      let context = {};
    
      let store = getServerStore();
    
      let domContent = renderToString(
          
            
              <>
                
    { routes.map(route => ) }
    > ); let html = ` react-ssr
    ${domContent}
    window.context = { state: ${JSON.stringify(store.getState())} } `; res.send(html); };

    관련 읽 기
  • React 서버 렌 더 링 의 길 01 - 프로젝트 기초 구조 구축
  • React 서버 렌 더 링 의 길 02 - 가장 간단 한 서버 렌 더 링
  • React 서버 렌 더 링 의 길 03 - 경로
  • React 서버 렌 더 링 의 길 04 - redux - 01
  • React 서버 렌 더 링 의 길 05 - redux - 02
  • React 서버 렌 더 링 의 길 06 - 최적화
  • React 서버 렌 더 링 의 길 07 - CSS 스타일 추가
  • React 서버 렌 더 링 의 길 08 - 404 와 리 셋
  • React 서버 렌 더 링 의 길 09 - SEO 최적화
  • 좋은 웹페이지 즐겨찾기