ASP에 React 통합NET Core는 Razzle과 SSR, 라우팅, 코드 분할, HMR 등 모든 좋은 제품을 사용합니다 – 섹션 2/2

34146 단어 razzlereactssrdotnet
본고의 첫 번째 부분에서 우리는 ASP에 서버 사이드 렌더링(SSR)이 있는 React 응용 프로그램을 만들었습니다.순수한 핵심.우리는 유명한 라이브러리ReactJS.NET를 사용하지 않고 다른 방식을 채택하여 우리로 하여금 더욱 큰 유연성을 가지게 했다.우리는 Razzle라는 도구를 사용했고 Javascript.NodeJS ASP에서 NodeJS를 호출했습니다.순수한 핵심.
이 부분에서, 우리는 클라이언트와 서버 측에 데이터를 추가할 것이다.우리는 코드 분할을 연구하고 응용 프로그램의 배치 가능한 패키지로 정리할 것이다.

데이터 로드


우리는 세 가지 상황에서 데이터에 접근할 수 있어야 한다.
  • 처음 응용 프로그램에 도착할 때 페이지를 완전히 보여주는 HTML을 되돌려야 합니다.이를 위해 우리는 서버 쪽에서 renderToString React 응용 프로그램을 보여주는 동시에 데이터를 React 응용 프로그램에 제공해야 한다.
  • 수합 과정에서 클라이언트에서 동일한 HTML을 보여야 합니다.이것은 우리가 다시 데이터를 필요로 한다는 것을 의미한다.
  • 마지막으로 클라이언트 라우팅을 수행하려면 AJAX를 사용하여 서버에서 데이터를 로드해야 합니다.
  • HeroController에서 사용할 간단한 메모리 데이터 제공 프로그램을 만듭니다.
    public class HeroDb : IHeroDb
    {
        private readonly Hero[] _items = new[]
        {
            new Hero{
                Id= 1,
                Name= "Luke Skywalker",
                Height= 172,
                Mass= 77,
                BirthYear= "19BBY",
            },
            ...
        };
    
        public Hero[] GetAll()=>_items;
        public Hero Get(int id)=>_items.SingleOrDefault(h => h.Id == id);
    }
    

    클라이언트 데이터 로드


    우선, 우리는 클라이언트의 루트를 주목할 것이다. 이것은 가장 간단한 부분이 될 것이다.우리는 단지 AJAX를 사용하여 데이터를 검색할 수 있을 뿐이다.업무를 간소화하기 위해서 우리는 항상 하나의 AJAX 호출을 사용하여 현재 페이지의 모든 데이터를 불러옵니다.
    컨트롤러에 두 개의 추가 동작을 추가합시다.
    public class HeroController : Controller
    {
    
        [Route("data/")]
        public IActionResult IndexData() => Ok(_db.GetAll());
    
        [Route("/data/{id:int}")]
        public IActionResult DetailData(int id) => Ok(_db.Get(id));
    }
    
    이 두 동작은 우리가 마지막으로 SSR을 위해 만든 동작에 대응합니다.각 URL에는 "data/"접두사가 있습니다.이렇게 하면 현재 페이지의 URL을 기반으로 페이지 데이터에 접근할 수 있는 약정이 생겼고 다른 설정이 필요하지 않습니다.실제 응용 프로그램에서 우리는 데이터와 비데이터 조작을 하나의 조작에 통합시켜 중복을 방지한다.예를 들어 우리는 URL을 다시 써서 그것을 실현할 수 있지만, 이것은 본문의 범위를 넘어섰다.
    클라이언트의 데이터를 한 위치에 불러오기 위해서, 우리는 간단한 고급 구성 요소를 도입할 것이다.
    const page = (WrappedComponent) =>
      ({ staticContext }) => {
        const location = useLocation();
    
        if (!staticContext) {
          useEffect(() => {
            fetch(`data${location.pathname}`)
              .then(r => r.json())
              .then(setPageData);
          }, [location]);
        }
    
        const [pageData, setPageData] = useState(null);
    
        return (
          pageData && <WrappedComponent pageData={pageData}></WrappedComponent>
        );
      };
    
    fetch API를 사용하여 데이터를 검색하고 있습니다.React router에서 제공하는 useLocation 갈고리는 URL을 구축하는 데 도움을 줍니다.예시에서 사용하지 않았기 때문에 검색 문자열을 무시했습니다.
    보시다시피 우리는 staticContext를 설정하지 않은 상태에서만 데이터를 검색합니다. 이것은 클라이언트에서 응용 프로그램을 보여주는 것을 의미합니다.잠시 후, 우리는 서버 측을 위해 다른 방법을 사용할 것이다.
    우리는 위치가 클라이언트 루트로 인해 변경될 때마다 데이터를 업데이트하기 위해 위치 의존성을 가진 효과 연결에서 데이터를 얻습니다.
    생산 코드에서, 우리는 낡은 요청에 대한 취소와 오류 처리를 추가할 것이지만, 예시를 간단하게 유지하기 위해서, 우리는 여기에서 그것을 생략할 것이다.
    페이지 구성 요소 덕분에 HeroList와 HeroDetail 구성 요소에 데이터를 쉽게 추가할 수 있습니다.
    const HeroList = ({ pageData }) => (
      <div>
        <h2>List of heroes</h2>
        <div>
          <ul>
            {pageData.map(hero => (
              <li key={hero.id}>
                <Link to={`/${hero.id}`}>{hero.name}</Link>
              </li>
            ))}
          </ul>
        </div>
      </div>
    );
    export default page(HeroList);
    
    const HeroDetail = ({ pageData }) => (
      <div>
        <h2>{pageData.name}</h2>
        <div>
          Height: {pageData.height}
        </div>
        <div>
          Mass: {pageData.mass}
        </div>
        <div>
          Year of birth: {pageData.birthYear}
        </div>
        <div>
          <Link to="/">Back to list</Link>
        </div>
      </div>
    );
    export default page(HeroDetail);
    

    서버 측 데이터 로드 및 워터 해싱


    서버에 데이터를 추가하기 위해서, ssresult와 Render Service 클래스를 작은 조정해야 합니다.
    public class SsrResult : IActionResult
    {
        ...
        private readonly object _data;
        public SsrResult(string url, object data)
        {
            ...
            _data = data;
        }
        public async Task ExecuteResultAsync(ActionContext context)
        {
            ...
            var renderResult = await renderService.RenderAsync(_url, _data);
            ...
        }
    }
    
    public class RenderService : IRenderService
    {
        ...
        public Task<string> RenderAsync(string url, object data) => 
            _nodeJSService.InvokeFromFileAsync<string>(_serverJsPath, 
                args: new object[] { url, data });
    }
    
    public class HeroController : Controller
    {
        ...
        [Route("/")]
        public IActionResult Index() => new SsrResult("/", _db.GetAll());
        [Route("/{id:int}")]
        public IActionResult Detail(int id) => new SsrResult("/:id", _db.Get(id));
        ...
    }
    
    SsrResult 구조 함수에서 데이터를 수신하여 서버에 직접 전달합니다.js는 RenderService 및 INodeJSService를 통해
    우리는 현재 서버의 데이터를 사용할 수 있다.js는 응용 프로그램을 보여 줍니다.
    const server = (cb, url, data) => {
      const context = { data };
      const markup = renderToString(
        <StaticRouter context={context} location={url}>
          <App />
        </StaticRouter>
      );
    
        ...
    
      cb(null, `<!doctype html>
          <html lang="">
          <head>
              ...
              <script>window.__ROUTE_DATA__=${JSON.stringify(data)}</script>
              ...
          </head>
          ...
        </html>`);
    }
    
    수신된 데이터는 StaticRouter의 상하문에 전달되어 페이지 구성 요소에 사용할 수 있습니다.내연 스크립트를 사용하면 수합 과정에서도 데이터에 접근할 수 있습니다.
    우리는 SSR과 수화 과정에서 페이지 고급 구성 요소의 데이터를 이용하려고 한다.
    const page = (WrappedComponent) =>
      ({ staticContext }) => {
        const location = useLocation();
    
        let initData = null;
        if (staticContext) {
          initData = staticContext.data;
        } else if (window.__ROUTE_DATA__) {
          initData = window.__ROUTE_DATA__;
          delete window.__ROUTE_DATA_;
        }
    
        if (!staticContext) {
          useEffect(() => {
            if (!initData) {
              fetch(`data${location.pathname}`)
            ...
    
    정적 컨텍스트 (SSR 중) 또는 _uroute\u data\uu 창 필드 (수합 중) 에서 데이터를 읽어들이고 있습니다.initData 변수를 채운 후\uu ROUTE\u DATA\uu 필드를 지우고 있음을 알 수 있습니다.이렇게 하면 초기 데이터는 클라이언트 루트 과정에서 다른 페이지에 사용되지 않고 수합 과정에서만 사용될 수 있습니다.
    브라우저를 확인해 보겠습니다.URL을 열면 초기 요청이 전체 HTML과 모든 데이터를 포함하는 것을 볼 수 있습니다.
    https://localhost:5000/4
    "목록 되돌리기"링크를 사용하여 목록을 탐색할 때, AJAX 호출이 하나만 실행된 것을 볼 수 있습니다.

    코드 분할


    우리는 현재 기능이 완비된 고체 계전기가 생겼다.현재 - 코드 분할이 지원하지 않는 멋진 기능을 추가할 때가 되었다.코드 분할을 통해 스크립트를 여러 블록으로 분할하고 필요할 때만 불러올 수 있습니다.이것은 사용자의 로드 시간이 더 빠르다는 것을 의미한다.
    우리는 ReactJS.NET 라이브러리를 사용할 것이다. Loadable Components 라이브러리와 달리 SSR도 지원한다.고맙습니다. Razzle은 불러올 수 있는 좋은 구성 요소 React.lazy 를 가지고 있기 때문에 우리의 작업은 상당히 쉬울 것입니다.
    우선, 우리는 약간의 의존항을 설치해야 한다.
    npm i @loadable/component @loadable/server -d
    npm i @loadable/babel-plugin @loadable/webpack-plugin -D
    
    지금 우리는 razzle를 업데이트할 수 있다.구성js는 다음 코드를 사용하여 설치된 패키지 플러그인을 포함합니다.
    if (target === "web") {
        const filename = path.resolve(__dirname, "build");
        config.plugins.push(
            new LoadableWebpackPlugin({
                outputAsset: false,
                writeToDisk: { filename },
            })
        );
    }
    
    구성 요소를 로드하려면 SSR이 제대로 작동하려면 Babel 플러그인(@Loadable/Babel-plugin)이 필요합니다.Razzle은 Razzle이 있는 폴더의 ".babelrc"파일을 통해 Babel 구성을 수정할 수 있습니다.구성네.Razzle은 초기화하는 동안 자동으로 선택됩니다.
    {
        "presets": [
            "razzle/babel"
        ],
        "plugins": [
            "@loadable/babel-plugin"
        ]
    }
    
    우리가 사용하는 것은razzle/babel 사전 설정입니다. 이것은 razzle이 제공하는 모든 기본값을 제공하기 때문에 수동으로 설정할 필요가 없습니다.
    다음에 블록 추출기를 불러올 수 있는 구성 요소에서 서버에 추가해야 합니다.js 파일.
    const server = (cb, url, data) => {
      const context = { data };
      const extractor = new ChunkExtractor({
        statsFile: path.resolve(__dirname, 'loadable-stats.json'),
        entrypoints: ['client'],
      });
      const markup = renderToString(
        <StaticRouter context={context} location={url}>
          <ChunkExtractorManager extractor={extractor}>
            <App />
          </ChunkExtractorManager>
        </StaticRouter>
      );
    
      const scriptTags = extractor.getScriptTags();
      const linkTags = extractor.getLinkTags();
      const styleTags = extractor.getStyleTags();
    
      cb(null, `<!doctype html>
          <html lang="">
          <head>
              <meta http-equiv="X-UA-Compatible" content="IE=edge" />
              <meta charset="utf-8" />
              <title>Welcome to Razzle</title>
              <meta name="viewport" content="width=device-width, initial-scale=1">
              <script>window.__ROUTE_DATA__=${JSON.stringify(data)}</script>
              ${linkTags}
              ${styleTags}
          </head>
          <body>
              <div id="root">${markup}</div>
              ${scriptTags}
          </body>
        </html>`);
    }
    
    또한 HTML 템플릿의 자산을 블록 추출기에서 가져온 자산으로 바꿉니다.
    우리는 두 페이지를 불러오는 것을 늦추고 싶기 때문에, 응용 프로그램에서 가져오는 것을 포장해야 한다.js 파일, 불러올 수 있는 구성 요소가 제공하는 불러올 수 있는 기능이 있습니다.
    const HeroList = loadable(() => import('./HeroList'))
    const HeroDetail = loadable(() => import('./HeroDetail'))
    
    응용 프로그램을 보여주는 데 필요한 모든 비동기적인 스크립트를 기다리기 위해서, 우리는 클라이언트에서 수화물을 봉인하여 호출해야 한다.loadableReady 함수가 있는 js입니다.
    loadableReady().then(() => {
      hydrate(
        ...
      );
    });
    
    이로써 우리는 코드를 응용 프로그램에 분할하는 통합을 완성했다.주의, 단지 우리가 ASP를 사용하기 때문에, 우리는 어떠한 특별한 일도 할 필요가 없다.NET Core는 우리의 백엔드로서 정말 훌륭합니다.

    인스턴스 응용 프로그램 게시


    본고의 앞부분에서 우리는 ASP가 제공하는 표준 React 템플릿을 사용하여 응용 프로그램을 안내했다.순수한 핵심.이 때문에, 발표 프로필은 우리를 위해 만들어진 것이기 때문에, 우리는 그 중의 어떤 줄도 변경할 필요가 없다.csproj 파일을 열면 PublishRunWebpack 대상이 실행 중인 것을 볼 수 있습니다
    npm install
    
    그리고
    npm run build
    
    npm 스크립트를 구축하는 것은 패키지에서 만든 것입니다.우리가 응용 프로그램의 클라이언트를 안내할 때,razzle 응용 프로그램을 만들어서 자동으로 json을 생성합니다.
    우리가 해야 할 유일한 일은 웹 패키지 설정을 작은 수정하는 것이다.Razzle은 서버 패키지에서 모든 node_모듈 패키지를 제외합니다.이것은 NodeJS 백엔드에 있어 의미가 있지만, 우리의 예에서 이것은 배치 과정을 더욱 어렵게 할 뿐이다.우리는 소포를 복사해야 한다.json, 가방 자물쇠.json, 대상 서버에 패키지를 설치합니다.우리로서는 웹팩이 모든 의존항을 생성된 가방에 묶는 것이 훨씬 쉽다. 우리는 이렇게 묶을 수 없는 의존항을 사용하지 않았다.
    razzle에 대해 마지막으로 수정합시다.구성js.
    if (dev) {
        ...
    } else {
        if (target === 'node') {
            config.externals = [];
        }
    }
    
    에서 웹 페이지 외부에 대한 더 많은 정보를 읽을 수 있습니다.
    우리는 완성했다.다음 명령을 사용하여 게시를 실행합니다.
    dotnet publish
    
    결과는 우리의 응용 프로그램의 전체 기능 패키지이다.

    웹 패키지 노드 외부 결론


    그러면 SSR React+ASP가 종료됩니다.NET Core 백엔드 통합개인적으로 이런 방식을 정말 좋아하는 것은 우리가 SSR이 역할을 발휘하기 위해 특수한 처리를 필요로 하는 React 라이브러리를 자유롭게 사용할 수 있다는 것이다.우리는 코드 분할과 같은 멋진 것을 사용할 수 있다. 아마도 웹팩이 미래에 제공할 어떤 것도 있을 것이다. 왜냐하면 우리는 ASP를 잘 결합시켰기 때문이다.NET Core 백엔드 및 패키지/반응 섹션
    예제 응용 프로그램의 전체 코드official documentation of Webpack에 액세스할 수 있습니다.

    좋은 웹페이지 즐겨찾기