ReactRouter의 구현 방법

17686 단어 ReactRouter실현

React Router의 구현

ReactRouterReact의 핵심 구성 요소로 주로 React의 루트 관리자로서 UIURL의 동기화를 유지하고 간단한API과 강력한 기능을 가진다. 예를 들어 코드 버퍼 불러오기, 동적 루트 일치, 정확한 위치 과도 처리 등이다.

묘사

React Routerhistory 대상 위에 세워진 것이다. 간단히 말하면 history 대상은 브라우저 주소 표시줄의 변화를 감청하는 방법을 알고 이것URLlocation 대상으로 전환시킨 다음에 router을 사용하여 루트에 일치하게 하고 마지막으로 대응하는 구성 요소를 정확하게 렌더링한다. 자주 사용하는 history은 세 가지 형식이 있다. Browser History, Hash History, Memory History.

Browser History

Browser HistoryReact Router의 응용 프로그램 추천history을 사용하고 브라우저의 History대상pushState,replaceStateAPIpopstate사건 등을 처리하여 URL처럼 진실한https://www.example.com/path을 만들 수 있습니다. 마찬가지로 페이지가 이동할 때 페이지를 다시 불러올 필요가 없습니다. 물론 서버에 대한 요청도 하지 않습니다.물론 URL 모델에 대해서는 백엔드의 설정 지원이 필요합니다. 비첫 페이지의 요청과 리셋 시 백엔드가 되돌아오는 자원을 지원하기 위해서입니다. 응용 프로그램은 단일 페이지 클라이언트 응용 프로그램이기 때문에 백엔드에 정확한 설정이 없으면 사용자가 브라우저에서 직접 방문history하면 되돌아오기 때문에 서버에 모든 상황을 덮어쓰는 후보 자원을 추가해야 합니다.URL 정적 자원이 일치하지 않으면 404 응용 프로그램 의존 페이지를 되돌려야 합니다. 예를 들어 URL 아래에 있는 설정입니다.

location / {
 try_files $uri $uri/ /index.html;
}

Hash History

index.html 기호 즉 Nginx 의 원래 목적은 Hash 에서 웹 페이지의 위치를 표시하는 데 사용됩니다. 예를 들어 #URLhttps://www.example.com/index.html#print 위치를 나타내고, 브라우저가 이것example을 읽으면 index.html 위치를 자동으로 가시 영역으로 스크롤합니다. 보통 print 라벨의 URL 속성이나 print 라벨의 <a> 속성을 사용하여 닻점을 지정합니다.name 속성을 통해 닻 위치를 읽을 수 있으며, <div>의 변경에 id 감청 이벤트를 추가할 수 있으며, 매번 변경window.location.hash할 때마다 브라우저 접근 역사에 기록을 추가합니다. 또한 Hashhashchange에는 나타나지만 Hash 요청에 포함되지 않습니다. 즉, Hash 및 그 후의 문자는 서버에 전송되어 자원이나 데이터의 요청을 하지 않습니다.브라우저 동작을 지도하는 데 사용되며 서버에 효과가 없기 때문에 변경 URL 은 페이지를 다시 불러오지 않습니다.HTTP의 역할은 변경#을 통해 페이지를 다시 요청하지 않는 상황에서 페이지 보기를 업데이트하여 동적 불러오기와 구성 요소를 없애는 것이다. 쉽게 말하면 주소 표시줄의 주소가 바뀌었지만 새로운 페이지가 아니라 이전의 페이지 일부분을 수정한 것이다. 이것도 Hash 단일 페이지 응용의 특징이다. 그 모든 활동은 하나ReactRouter 페이지에 국한된다.게으르지 않은 페이지는 이 URL 페이지가 초기화될 때만 해당하는 SPA, Web, Web 파일을 불러옵니다. 페이지 불러오기가 완료되면 HTML 페이지를 다시 불러오거나 이동하지 않고 JavaScript 동적 변환CSS을 사용합니다. 기본SPA 모드는 닻을 통해 루트를 실현하고 구성 요소의 표시와 숨김을 제어하여 페이지 이동과 유사한 상호작용을 합니다.

Memory History

JavaScript 주소 표시줄에서 조작되거나 읽히지 않기 때문에 서버 렌더링을 어떻게 실현하는지 설명할 수 있을 뿐만 아니라 테스트와 다른 렌더링 환경(예를 들어 HTML과 다른 두 가지Hash의 차이점은 우리가 그것을 만들어야 한다는 것이다. 이런 방식은 테스트에 편리하다.

const history = createMemoryHistory(location);

실현


우리는 매우 간단한 Memory History모드와 React Native모드의 실현을 실현한다. HistoryBrowser History방법은 로컬 파일 프로토콜Hash History에서 실행할 수 없기 때문에 실행하기 위해서는 H5환경을 구축해야 한다. 사용pushState,file://,http://등은 모두 가능하고 webpack모드로 돌아가야 한다.Nginx 루트 점프를 실현하고 페이지를 갱신하지 않는 것은 Apache가 제공한 Browser History, history 등 방법과 H5 등 사건 덕분이다. 이런 방법은 루트 경로를 바꿀 수도 있지만 페이지 점프를 하지 않는다. 물론 백엔드가 설정되지 않은 상황에서 루트 개편 후 페이지를 갱신하면 pushState() 모델을 제시할 수 있다. replaceState() 모델에 대해 우리의 실현 방향은 비슷하다.주로 popstate404을 사용하지 않은Hash History, 그리고 감청 사건과 달리 pushState 사건의 변화를 감청한 다음에 해당H5을 받아 대응하는 보기를 업데이트하는 데 있다.

<!-- Browser History -->
<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <title>Router</title>
</head>

<body>
 <ul>
  <li><a href="/home" rel="external nofollow" >home</a></li>
  <li><a href="/about" rel="external nofollow" >about</a></li>
  <div id="routeView"></div>
 </ul>
</body>
<script>
 function Router() {
  this.routeView = null; //  
  this.routes = Object.create(null); //  
 }

 //  
 Router.prototype.route = function (path, callback) {
  this.routes[path] = () => this.routeView.innerHTML = callback() || "";
 };

 //  
 Router.prototype.init = function(root, rootView) {
  this.routeView = rootView; //  
  this.refresh(); //  
  root.addEventListener("click", (e) => { //  root
   if (e.target.nodeName === "A") {
    e.preventDefault();
    history.pushState(null, "", e.target.getAttribute("href"));
    this.refresh(); //  
   }
  })
  //  
  // pushState replaceState popstate 
  window.addEventListener("popstate", this.refresh.bind(this), false); 
 };

 //  
 Router.prototype.refresh = function () {
  let path = location.pathname;
  console.log("refresh", path);
  if(this.routes[path]) this.routes[path]();
  else this.routeView.innerHTML = "";
 };

 window.Router = new Router();
 
 Router.route("/home", function() {
  return "home";
 });
 Router.route("/about", function () {
  return "about";
 });

 Router.init(document, document.getElementById("routeView"));

</script>
</html>

<!-- Hash History -->
<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <title>Router</title>
</head>

<body>
 <ul>
  <li><a href="#/home" rel="external nofollow" >home</a></li>
  <li><a href="#/about" rel="external nofollow" >about</a></li>
  <div id="routeView"></div>
 </ul>
</body>
<script>
 function Router() {
  this.routeView = null; //  
  this.routes = Object.create(null); //  
 }

 //  
 Router.prototype.route = function (path, callback) {
  this.routes[path] = () => this.routeView.innerHTML = callback() || "";
 };

 //  
 Router.prototype.init = function(root, rootView) {
  this.routeView = rootView; //  
  this.refresh(); //  
  //  hashchange 
  window.addEventListener("hashchange", this.refresh.bind(this), false); 
 };

 //  
 Router.prototype.refresh = function () {
  let hash = location.hash;
  console.log("refresh", hash);
  if(this.routes[hash]) this.routes[hash]();
  else this.routeView.innerHTML = "";
 };

 window.Router = new Router();
 
 Router.route("#/home", function() {
  return "home";
 });
 Router.route("#/about", function () {
  return "about";
 });

 Router.init(document, document.getElementById("routeView"));

</script>
</html>

분석

  • 의 실현을 살펴볼 수 있다. APIhashchange, location.hashReactRouter이다. 그 전에 commit id 라이브러리, eef79d5 라이브러리, TAG에 의존하는 대4.4.0 강화판의 history 라이브러리를 알아야 한다. 그 중에서 주로 history 대상이 현재의 ReactRouter 라이브러리와 window.history 일치하는 결과를 나타낸다.history 대상은 match 라이브러리 기반URL의 파생이다.
  • path는 루트를 몇 개의 패키지로 분리했다. location는 일반적인 루트 논리를 맡고 history는 브라우저의 루트 관리를 맡으며 window.locationReactRouter의 루트 관리를 맡는다.
  • 우리는 react-router 구성 요소를 예로 들면, react-router-domreact-router-native 에서 고급 구성 요소로 내부에 전체적인 react-native 대상을 만들고, 전체 루트의 변화를 감청하고, BrowserRouterBrowserRouter 구성 요소로 전달할 수 있으며, react-router-dom 구성 요소는 이 history 의 속성을 history 하위 구성 요소로 다시 전달할 수 있다.
  • 
    // packages\react-router-dom\modules\HashRouter.js line 10
    class BrowserRouter extends React.Component {
     history = createHistory(this.props);
    
     render() {
     return <Router history={this.history} children={this.props.children} />;
     }
    }
    다음으로 우리는 props 구성 요소에 가서 react-router 구성 요소는 Router 환경을 만들었고 Router 을 빌려 historycontext 를 전달했다. 이것은 왜 Router 모든 Router 바깥에 있어야 하는지를 설명한다.React ContextcontextRoute를 추가하면 루트의 변화를 감청하고 리셋 이벤트를 실행할 수 있으며 여기서 촉발된다context.Router시 즉 매번 루트가 변할 때Route 촉발 정상층Router의 리셋 이벤트componentWillMounthistory.listensetState아래로 전달setState이때->에 최신Router-> 아래Router를 포함하여 새로운setState을 획득하여 렌더링 여부를 판단한다.
    
    // line packages\react-router\modules\Router.js line 10
    class Router extends React.Component {
     static computeRootMatch(pathname) {
     return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
     }
    
     constructor(props) {
     super(props);
    
     this.state = {
      location: props.history.location
     };
    
     // This is a bit of a hack. We have to start listening for location
     // changes here in the constructor in case there are any <Redirect>s
     // on the initial render. If there are, they will replace/push when
     // they mount and since cDM fires in children before parents, we may
     // get a new location before the <Router> is mounted.
     this._isMounted = false;
     this._pendingLocation = null;
    
     if (!props.staticContext) {
      this.unlisten = props.history.listen(location => {
      if (this._isMounted) {
       this.setState({ location });
      } else {
       this._pendingLocation = location;
      }
      });
     }
     }
    
     componentDidMount() {
     this._isMounted = true;
    
     if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation });
     }
     }
    
     componentWillUnmount() {
     if (this.unlisten) this.unlisten();
     }
    
     render() {
     return (
      <RouterContext.Provider
      children={this.props.children || null}
      value={{
       history: this.props.history,
       location: this.state.location,
       match: Router.computeRootMatch(this.state.location.pathname),
       staticContext: this.props.staticContext
      }}
      />
     );
     }
    }
    우리는 사용할 때 -> 를 끼워 넣는 것을 사용했기 때문에 이때 nextContext 구성 요소, context 의 역할은 일치하는 루트를 사용하고 렌더링할 구성 요소location, -> 상층부Route 전송된 nextContext, Router 중의 Route 를 받아들여 전체 페이지의 루트 변화를 감청하고 페이지가 전환될 때 감청Route 이벤트를 촉발한다.Route 아래로 전달props하면 RouteRoutercontext를 업데이트하여 현재Routerhistory 일치 여부history를 판단하고, 일치하면 렌더링하고, 그렇지 않으면 렌더링하지 않으며, 일치 여부의 근거는 Router 이 함수입니다. 다음은 분석할 것입니다. 여기에서 일치 실패는nextContextRoute만 알면 됩니다.일치에 성공하면 props 결과를 context 의 일부로 Route 에서 렌더링할 구성 요소에 전달합니다.path 세 가지 유형location, computeMatch, match, null을 받아들여야 한다. 이때 주의해야 할 것은 들어오는 match이 내연 함수라면 매번props이 새로 만들어지기 때문에 render 새로운 구성 요소가 들어왔다고 생각하기 때문에 낡은 구성 요소Route를 다시 render props 받아들인다.이때 <Route component>를 사용해야 한다. 패키지의 <Route render> 요소가 한 겹 없어지고, <Route children> 전개된 원소의 유형이 매번 같으면 component 발생하지 않고, 그 밖에 props.component 발생하지 않는다React.
    
    // \packages\react-router\modules\Route.js line 17
    class Route extends React.Component {
     render() {
     return (
      <RouterContext.Consumer>
      {context => {
       invariant(context, "You should not use <Route> outside a <Router>");
    
       const location = this.props.location || context.location;
       const match = this.props.computedMatch
       ? this.props.computedMatch // <Switch> already computed the match for us
       : this.props.path
        ? matchPath(location.pathname, this.props)
        : context.match;
    
       const props = { ...context, location, match };
    
       let { children, component, render } = this.props;
    
       // Preact uses an empty array as children by
       // default, so use null if that's the case.
       if (Array.isArray(children) && children.length === 0) {
       children = null;
       }
    
       if (typeof children === "function") {
       children = children(props);
       // ...
       }
    
       return (
       <RouterContext.Provider value={props}>
        {children && !isEmptyChildren(children)
        ? children
        : props.match
         ? component
         ? React.createElement(component, props)
         : render
          ? render(props)
          : null
         : null}
       </RouterContext.Provider>
       );
      }}
      </RouterContext.Consumer>
     );
     }
    }
    우리가 실제로 가장 많이 쓸 수 있는 것은 바로 diff 라벨이다. 그래서 unmount 구성 요소를 다시 한 번 보면 re-mount 결국 render 라벨을 만들어서 돌릴 요소를 감싸는 것을 볼 수 있다. 이 component 라벨의 render 클릭 사건에서 re-mount 기본 돌림을 금지하기 때문에 실제로 이곳children은 실제적인 작용이 없다.그러나 이동할 페이지의 re-mount 를 표시할 수 있고 더 좋은 Link 의미가 있습니다.<Link>Link, 마우스 왼쪽 단추로 눌리지 않은, 비a점프하지 않은, 다른 기능키를 누르지 않은 클릭a, 그리고handleClickpreventDefault에 들어간 것도 앞에서 말한 루트의 변화가 페이지의 점프와 서로 관련이 없는 것이고, href URL 라이브러리html를 통해 handleClick 호출된 것이다.그러나 이것은 루트만 변화시킬 뿐 다른 것은 아무것도 변하지 않는다.preventDefault 중의 _blank 루트의 변화를 감청하고 preventDefault 업데이트 pushhistory 하위 ReactRouter 를 통해 다시 일치하게 하여 렌더링 부분의 업데이트를 완성합니다.
    
    // packages\react-router-dom\modules\Link.js line 14
    class Link extends React.Component {
     handleClick(event, history) {
     if (this.props.onClick) this.props.onClick(event);
    
     if (
      !event.defaultPrevented && // onClick prevented default
      event.button === 0 && // ignore everything but left clicks
      (!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc.
      !isModifiedEvent(event) // ignore clicks with modifier keys
     ) {
      event.preventDefault();
    
      const method = this.props.replace ? history.replace : history.push;
    
      method(this.props.to);
     }
     }
    
     render() {
     const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars
    
     return (
      <RouterContext.Consumer>
      {context => {
       invariant(context, "You should not use <Link> outside a <Router>");
    
       const location =
       typeof to === "string"
        ? createLocation(to, null, null, context.location)
        : to;
       const href = location ? context.history.createHref(location) : "";
    
       return (
       <a
        {...rest}
        onClick={event => this.handleClick(event, context.history)}
        href={href}
        ref={innerRef}
       />
       );
      }}
      </RouterContext.Consumer>
     );
     }
    }
    매일 한 문제
    https://github.com/WindrunnerMax/EveryDay
    참고
    https://zhuanlan.zhihu.com/p/44548552 https://github.com/fi3ework/blog/issues/21 https://juejin.cn/post/6844903661672333326 https://juejin.cn/post/6844904094772002823 https://juejin.cn/post/6844903878568181768 https://segmentfault.com/a/1190000014294604 https://github.com/youngwind/blog/issues/109 http://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html
    이 React Router의 실현 방법에 관한 글은 여기 소개되어 있습니다. 더 많은 React Router의 실현 내용은 저희 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기