>웹 프론트엔드 >JS 튜토리얼 >React 서버사이드 렌더링을 사용하는 방법

React 서버사이드 렌더링을 사용하는 방법

php中世界最好的语言
php中世界最好的语言원래의
2018-05-31 09:48:331364검색

이번에는 React 서버사이드 렌더링 사용법과 React 서버사이드 렌더링 사용 시 주의사항에 대해 알려드리겠습니다. 실제 사례를 살펴보겠습니다.

React는 컴포넌트(Virtual DOM)를 HTML String으로 출력하기 위해 renderToString과 renderToStaticMarkup이라는 두 가지 메소드를 제공합니다. 이는 React 서버 측 렌더링의 기본이므로 브라우저 환경에 대한 서버 측 의존성을 제거합니다. 측면에서 매력적인 것을 렌더링합니다.

브라우저 환경에 대한 의존성을 해결하는 것 외에도 서버 측 렌더링은 두 가지 문제도 해결해야 합니다.

  1. 프런트엔드와 백엔드는 코드를 공유할 수 있습니다. 최종 라우팅은 균일하게 처리될 수 있습니다

  2. React 생태계는 많은 것을 제공합니다. 솔루션을 선택하세요. 여기에서는 설명을 위해 Redux와 React-Router를 선택합니다.

Redux

Redux는 Flux와 유사한 단방향 데이터 흐름 세트를 제공합니다. 전체 애플리케이션은 하나의 Store만 유지하며 기능 지향적 기능을 통해 서버 측 렌더링 지원에 친숙합니다.

Redux의 작동 방식을 배우는 데 2분

스토어 정보:

전체 애플리케이션에는 단 하나의 Store만 있습니다

  1. 스토어의 해당 상태 트리(State)는 리듀서에 의해 호출됩니다. 함수(루트 리듀서)는

  2. 상태 트리의 각 필드를 다양한 리듀서 함수에 의해 추가로 생성할 수 있습니다.

  3. Store에는 데이터 흐름을 처리하기 위해 디스패치, getState와 같은 여러 메소드가 포함되어 있습니다.

  4. Store의 상태 트리만

  5. Redux의 데이터 흐름에 대한 변경 사항은 dispatch(action)에 의해 트리거될 수 있습니다.

action은 { type, payload }를 포함하는 객체입니다.

  1. reducer 함수는 store.dispatch(action)에 의해 트리거됩니다.

  2. 리듀서 함수는 두 개의 매개변수(상태, 동작)를 받아들이고 새로운 상태를 반환합니다

  3. 리듀서 함수는 action.type을 결정한 다음 해당 action.payload 데이터를

    update
  4. 상태 트리
  5. 로 처리합니다. 예를 들어 Store는 UI 스냅샷에 해당하고 서버 측 렌더링은 서버 측에서 Store를 초기화하고 Store를 애플리케이션의 루트 구성 요소에 전달하고 루트 구성 요소에서 renderToString을 호출하여 전체 애플리케이션을 초기화 데이터가 포함된 HTML로 변환합니다.

react-router

react-router는 페이지에 다양한 구성요소를 표시하기 위해 선언적 방식으로 다양한 라우팅 결정을 일치시키고, 라우팅이 변경되는 한 props를 통해 라우팅 정보를 구성요소에 전달합니다. 구성 요소가 변경되고 다시 렌더링되도록 트리거됩니다. 목록 페이지 /list와 세부정보 페이지 /item/:id라는 두 페이지만 있는 매우 간단한 애플리케이션이 있다고 가정해 보겠습니다. 목록에서 항목을 클릭하여 세부정보 페이지로 들어갑니다.

다음과 같이 경로를 정의할 수 있습니다

, ./routes.js

import React from 'react';
import { Route } from 'react-router';
import { List, Item } from './components';
// 无状态(stateless)组件,一个简单的容器,react-router 会根据 route
// 规则匹配到的组件作为 `props.children` 传入
const Container = (props) => {
 return (
  <p>{props.children}</p>
 );
};
// route 规则:
// - `/list` 显示 `List` 组件
// - `/item/:id` 显示 `Item` 组件
const routes = (
 <Route path="/" component={Container} >
  <Route path="list" component={List} />
  <Route path="item/:id" component={Item} />
 </Route>
);
export default routes;
여기서부터 이 매우 간단한 애플리케이션을 사용하여 서버 측 렌더링 프런트엔드 및 백엔드 구현과 관련된 세부 사항 중 일부를 설명합니다.

Reducer

Store는 Reducer에 의해 생성되므로 Reducer는 실제로 Store의 상태 트리 구조를 반영합니다./reducers/index.js

import listReducer from './list';
import itemReducer from './item';
export default function rootReducer(state = {}, action) {
 return {
  list: listReducer(state.list, action),
  item: itemReducer(state.item, action)
 };
}
rootReducer의 상태 매개변수는 전체 Store의 상태 트리입니다. 상태 트리 아래의 각 필드에는 자체 리듀서가 있을 수 있으므로 여기서는 listReducer 및 itemReducer를 소개합니다. 이 두 리듀서의 상태 매개변수는 전체 상태 트리의 해당 목록 및 항목 필드일 뿐입니다.

./reducers/list.js

const initialState = [];
export default function listReducer(state = initialState, action) {
 switch(action.type) {
 case 'FETCH_LIST_SUCCESS': return [...action.payload];
 default: return state;
 }
}
list에 특정한 항목은 다음 구조와 유사할 수 있는 항목을 포함하는 간단한 배열입니다. [{ id: 0, name: 'first item'}, {id: 1, name : '두 번째 항목'}], 'FETCH_LIST_SUCCESS'의 action.payload에서 가져옵니다.

그런 다음 ./reducers/item.js에서 획득한 항목 데이터를 처리합니다

const initialState = {};
export default function listReducer(state = initialState, action) {
 switch(action.type) {
 case 'FETCH_ITEM_SUCCESS': return [...action.payload];
 default: return state;
 }
}

Action

对应的应该要有两个 action 来获取 list 和 item,触发 reducer 更改 Store,这里我们定义 fetchList 和 fetchItem 两个 action。

./actions/index.js

import fetch from 'isomorphic-fetch';
export function fetchList() {
 return (dispatch) => {
  return fetch('/api/list')
    .then(res => res.json())
    .then(json => dispatch({ type: 'FETCH_LIST_SUCCESS', payload: json }));
 }
}
export function fetchItem(id) {
 return (dispatch) => {
  if (!id) return Promise.resolve();
  return fetch(`/api/item/${id}`)
    .then(res => res.json())
    .then(json => dispatch({ type: 'FETCH_ITEM_SUCCESS', payload: json }));
 }
}

isomorphic-fetch 是一个前后端通用的 Ajax 实现,前后端要共享代码这点很重要。

另外因为涉及到异步请求,这里的 action 用到了 thunk,也就是函数,redux 通过 thunk-middleware 来处理这类 action,把函数当作普通的 action dispatch 就好了,比如 dispatch(fetchList())

Store

我们用一个独立的 ./store.js,配置(比如 Apply Middleware)生成 Store

import { createStore } from 'redux';
import rootReducer from './reducers';
// Apply middleware here
// ...
export default function configureStore(initialState) {
 const store = createStore(rootReducer, initialState);
 return store;
}

react-redux

接下来实现 组件,然后把 redux 和 react 组件关联起来,具体细节参见 react-redux

./app.js

import React from 'react';
import { render } from 'react-dom';
import { Router } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import { Provider } from 'react-redux';
import routes from './routes';
import configureStore from './store';
// `INITIAL_STATE` 来自服务器端渲染,下一部分细说
const initialState = window.INITIAL_STATE;
const store = configureStore(initialState);
const Root = (props) => {
 return (
  <p>
   <Provider store={store}>
    <Router history={createBrowserHistory()}>
     {routes}
    </Router>
   </Provider>
  </p>
 );
}
render(<Root />, document.getElementById('root'));

至此,客户端部分结束。

Server Rendering

接下来的服务器端就比较简单了,获取数据可以调用 action,routes 在服务器端的处理参考 react-router server rendering,在服务器端用一个 match 方法将拿到的 request url 匹配到我们之前定义的 routes,解析成和客户端一致的 props 对象传递给组件。

./server.js

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RoutingContext, match } from 'react-router';
import { Provider } from 'react-redux';
import routes from './routes';
import configureStore from './store';
const app = express();
function renderFullPage(html, initialState) {
 return `
  <!DOCTYPE html>
  <html lang="en">
  <head>
   <meta charset="UTF-8">
  </head>
  <body>
   <p id="root">
    <p>
     ${html}
    </p>
   </p>
   <script>
    window.INITIAL_STATE = ${JSON.stringify(initialState)};
   </script>
   <script src="/static/bundle.js"></script>
  </body>
  </html>
 `;
}
app.use((req, res) => {
 match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
  if (err) {
   res.status(500).end(`Internal Server Error ${err}`);
  } else if (redirectLocation) {
   res.redirect(redirectLocation.pathname + redirectLocation.search);
  } else if (renderProps) {
   const store = configureStore();
   const state = store.getState();
   Promise.all([
    store.dispatch(fetchList()),
    store.dispatch(fetchItem(renderProps.params.id))
   ])
   .then(() => {
    const html = renderToString(
     <Provider store={store}>
      <RoutingContext {...renderProps} />
     </Provider>
    );
    res.end(renderFullPage(html, store.getState()));
   });
  } else {
   res.status(404).end('Not found');
  }
 });
});

服务器端渲染部分可以直接通过共用客户端 store.dispatch(action) 来统一获取 Store 数据。另外注意 renderFullPage 生成的页面 HTML 在 React 组件 mount 的部分(

),前后端的 HTML 结构应该是一致的。然后要把 store 的状态树写入一个全局变量(INITIAL_STATE),这样客户端初始化 render 的时候能够校验服务器生成的 HTML 结构,并且同步到初始化状态,然后整个页面被客户端接管。

最后关于页面内链接跳转如何处理?

react-router 提供了一个 组件用来替代 标签,它负责管理浏览器 history,从而不是每次点击链接都去请求服务器,然后可以通过绑定 onClick 事件来作其他处理。

比如在 /list 页面,对于每一个 item 都会用 绑定一个 route url:/item/:id,并且绑定 onClick 去触发 dispatch(fetchItem(id)) 获取数据,显示详情页内容。

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

怎样使用JS实现计算圆周率到小数点后100位

怎样使用vue axios 给生产与发布环境配置接口地址

위 내용은 React 서버사이드 렌더링을 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.