>  기사  >  웹 프론트엔드  >  React Router의 핵심 히스토리 라이브러리에 대한 자세한 분석

React Router의 핵심 히스토리 라이브러리에 대한 자세한 분석

不言
不言원래의
2018-08-14 11:01:563098검색

이 글은 React Router의 핵심 히스토리 라이브러리에 대한 자세한 분석을 소개합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

Preface

React를 사용하여 약간 더 복잡한 애플리케이션을 개발하는 경우 React Router는 라우팅 관리를 위한 거의 유일한 선택입니다. React Router는 네 가지 주요 버전 업데이트를 거쳤고 그 기능이 점점 더 풍부해졌음에도 불구하고 어떻게 변화하든 히스토리 라이브러리에 대한 핵심 의존성은 변하지 않았습니다. github의 4,000개 이상의 별이 포함된 이 라이브러리가 어떤 기능을 제공하는지 살펴보겠습니다.

HTML5 히스토리 객체

히스토리 라이브러리에 관해 이야기하자면, 이 단어가 조금 익숙하다고 생각하시나요? 예, 동일한 이름을 가진 새로운 기록 개체도 HTML5 사양에 추가되었습니다. 이 히스토리 객체가 어떤 문제를 해결하는 데 사용되는지 살펴보겠습니다.

jQuery가 프런트엔드를 장악하던 시대에는 새로 고치지 않는 페이지 업데이트를 ajax를 통해 요청하는 것이 당시 매우 인기 있는 페이지 처리 방법이었습니다. 당시 SPA의 프로토타입이 발전했습니다. 새로 고친 후에도 올바른 페이지 요소가 계속 표시될 수 있도록 페이지 변경 사항을 표시하기 위해 일반적으로 URL의 해시 값이 페이지를 고유하게 찾을 수 있도록 변경됩니다. 그러나 이로 인해 또 다른 문제가 발생합니다. 사용자는 페이지를 전환하기 위해 앞으로/뒤로를 사용할 수 없습니다.

이 문제를 해결하기 위해 히스토리 객체가 탄생했습니다. 페이지의 URL이나 해시가 변경되면 브라우저는 자동으로 새 URL을 기록 개체에 푸시합니다. 상태 배열은 URL의 변경 사항을 기록하기 위해 기록 개체 내부에 유지됩니다. 브라우저가 앞으로/뒤로 작업을 수행할 때 실제로는 기록 개체의 해당 메서드(forward/back)를 호출하고 해당 상태를 꺼내며 페이지를 전환합니다. . forward/back),取出对应的state,从而进行页面的切换。

除了操作url,history对象还提供2个不用通过操作url也能更新内部state的方法,分别是pushStatereplaceState。还能将额外的数据存到state中,然后在onpopstate事件中再通过event.state取出来。如果希望对history对象作更深入的理解,可以参考 这里,和这里。

history库与HTML5 history对象的关系

我们再回过头来看history库。它本质上做了以下4件事情:

  1. 借鉴HTML5 history对象的理念,在其基础上又扩展了一些功能

  2. 提供3种类型的history:browserHistory,hashHistory,memoryHistory,并保持统一的api

  3. 支持发布/订阅功能,当history发生改变的时候,可以自动触发订阅的函数

  4. 提供跳转拦截、跳转确认和basename等实用功能

再对比一些两者api的异同。以下是history库的:

const history = {
    length,        // 属性,history中记录的state的数量
    action,        // 属性,当前导航的action类型
    location,      // 属性,location对象,封装了pathname、search和hash等属性
    push,          // 方法,导航到新的路由,并记录在history中
    replace,       // 方法,替换掉当前记录在history中的路由信息
    go,            // 方法,前进或后退n个记录
    goBack,        // 方法,后退
    goForward,     // 方法,前进
    canGo,         // 方法,是否能前进或后退n个记录
    block,         // 方法,跳转前让用户确定是否要跳转
    listen         // 方法,订阅history变更事件
  };

以下是HTML5 history对象的:

const history = {
    length,         // 属性,history中记录的state的数量
    state,          // 属性,pushState和replaceState时传入的对象
    back,           // 方法,后退
    forward,        // 方法,前进
    go,             // 方法,前进或后退n个记录
    pushState,      // 方法,导航到新的路由,并记录在history中
    replaceState    // 方法,替换掉当前记录在history中的路由信息
}

// 订阅history变更事件
window.onpopstate = function (event) {
    ...
}

从对比中可以看出,两者的关系是非常密切的,history库可以说是history对象的超集,是功能更强大的history对象。

createHashHistory源码分析

下面,我们以三种history类型中的一种,hashHistory为例,来分析下history的源码,看看它都干了些什么。先看下它是怎么处理hash变更的。

// 构造hashHistory对象
const createHashHistory = (props = {}) => {
    ...
    const globalHistory = window.history;    // 引用HTML5 history对象
    ...
    // transitionManager负责控制是否进行跳转,以及跳转后要通知到的订阅者,后面会详细讨论
    const transitionManager = createTransitionManager();
    ...
    // 注册history变更回调的订阅者
    const listen = listener => {
        const unlisten = transitionManager.appendListener(listener);
        checkDOMListeners(1);

        return () => {
            checkDOMListeners(-1);
            unlisten();
        };
    };
    
    // 监听hashchange事件
    const checkDOMListeners = delta => {
        listenerCount += delta;

        if (listenerCount === 1) {
            window.addEventListener(HashChangeEvent, handleHashChange);
        } else if (listenerCount === 0) {
            window.removeEventListener(HashChangeEvent, handleHashChange);
        }
    };
    
    // hashchange事件回调
    const handleHashChange = () => {
        ...
        // 构造内部使用的location对象,包含pathname、search和hash等属性
        const location = getDOMLocation();    
        ...
        handlePop(location);
    };
    
    // 处理hash变更逻辑
    const handlePop = location => {
        ...
        const action = "POP";
        // 给用户展示确认跳转的信息(如果有的话),确认后通知订阅者。如果用户取消跳转,则回退到之前状态
        transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => {
            if (ok) {
                setState({action, location});    // 确认后通知订阅者
            } else {
                revertPop(location);             // 取消则回退到之前状态
            }
        });
    };
    
    // 更新action,location和length属性,并通知订阅者
    const setState = nextState => {
        Object.assign(history, nextState);

        history.length = globalHistory.length;

        transitionManager.notifyListeners(history.location, history.action);
    };
    ...
}

以上就是处理被动的hash变更的逻辑,一句话概括就是:订阅hash变更事件,判断是否确实要变更,如需变更则更新自己的属性,通知订阅者,不需变更则回退到之前的状态。

下面再看下transitionManager做了什么,重点看发布/订阅相关内容,忽略用户确认跳转相关内容。

const createTransitionManager = () => {
    ...
    // 内部维护的订阅者列表
    let listeners = [];

    // 注册订阅者
    const appendListener = fn => {
        let isActive = true;

        const listener = (...args) => {
            if (isActive) fn(...args);
        };

        listeners.push(listener);

        return () => {
            isActive = false;
            listeners = listeners.filter(item => item !== listener);
        };
    };

    //通知订阅者
    const notifyListeners = (...args) => {
        listeners.forEach(listener => listener(...args));
    };
    ...
}

这里的代码一目了然,就是维护一个订阅者列表,当hash变更的时候通知到相关的函数。

以上是hash改变的时候被动更新相关的内容,下面再看下主动更新相关的代码,以push为例,replace大同小异。

const push = (path, state) => {
    ...
    const action = "PUSH";
    const location = createLocation(path, undefined, undefined, history.location);

    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => {
        if (!ok)     // 如果取消,则不跳转
            return;
        ...
        pushHashPath(encodedPath);        // 用新的hash替换到url当中
        ...
        setState({action, location});     // 更新action,location和length属性,并通知订阅者

    });
};

// 用新的hash替换到url当中
const pushHashPath = path => (window.location.hash = path);

在浏览器进行前进后退操作时,history库实际上是通过操作HTML5 history对象实现的。

const globalHistory = window.history;

const go = n => {
    ...
    globalHistory.go(n);
};

const goBack = () => go(-1);

const goForward = () => go(1);

当调用window.history.go

기록 개체는 URL을 작동하는 것 외에도 URL을 작동하지 않고 내부 상태를 업데이트할 수 있는 두 가지 메서드, 즉 pushStatereplaceState를 제공합니다. 또한 상태에 추가 데이터를 저장한 다음 onpopstate 이벤트의 event.state를 통해 검색할 수도 있습니다. 히스토리 객체에 대한 더 깊은 이해를 원하시면 여기와 여기를 참고하시면 됩니다.

히스토리 라이브러리와 HTML5 히스토리 객체의 관계

다시 돌아가서 히스토리 라이브러리를 살펴보겠습니다. 기본적으로 다음 4가지 작업을 수행합니다.

  1. HTML5 기록 개체의 개념을 활용하고 이를 기반으로 일부 기능을 확장합니다.

    #🎜 🎜#
  2. browserHistory, hashHistory, memoryHistory 3가지 유형의 기록을 제공하고 통합 API를 유지합니다.
  3. 기록/구독 기능 지원 기록 변경 시 , 구독 기능이 자동으로 실행될 수 있습니다
  4. 점프 차단, 점프 확인 및 베이스 이름과 같은 실용적인 기능을 제공합니다
  5. #🎜 🎜##🎜 🎜# 두 API의 몇 가지 유사점과 차이점을 비교해 보겠습니다. 다음은 히스토리 라이브러리입니다:

    rrreee
  6. 다음은 HTML5 히스토리 객체입니다:
rrreee

비교를 보면 둘 사이의 관계가 매우 가깝다는 것을 알 수 있습니다. 히스토리 라이브러리는 히스토리 객체의 상위 집합이자 더 강력한 히스토리 객체라고 할 수 있습니다.

#🎜🎜#createHashHistory 소스코드 분석 #🎜🎜##🎜🎜# 아래에서는 히스토리 소스코드 3가지 중 하나인 hashHistory를 예로 들어 히스토리 소스코드를 분석해 무슨 일을 했는지 살펴보겠습니다. 먼저 해시 변경을 처리하는 방법을 살펴보겠습니다. #🎜🎜#rrreee#🎜🎜#위는 패시브 해시 변경을 처리하는 로직을 한 문장으로 요약하면 다음과 같습니다. 해시 변경 이벤트를 구독하고, 변경이 정말 필요한지 확인하고, 자신의 속성을 업데이트하세요. 변경이 필요한 경우 구독자에게 알립니다. 필요하지 않습니다. 변경 사항은 이전 상태로 롤백됩니다. #🎜🎜##🎜🎜# 전환 관리자가 수행하는 작업을 살펴보겠습니다. 게시/구독과 관련된 콘텐츠에 집중하고 사용자 확인 점프와 관련된 콘텐츠는 무시합니다. #🎜🎜#rrreee#🎜🎜#여기 코드는 한눈에 명확합니다. 구독자 목록을 유지하고 해시가 변경될 때 관련 기능에 알리는 것입니다. #🎜🎜##🎜🎜#위는 해시 변경 시 수동 업데이트에 관련된 내용입니다. push를 예로 들어 replace해 보겠습니다. 거의 동일합니다. #🎜🎜#rrreee#🎜🎜#브라우저에서 정방향, 역방향 작업을 수행할 때 HTML5 히스토리 객체를 연산하여 실제로 히스토리 라이브러리를 구현합니다. #🎜🎜#rrreee#🎜🎜#window.history.go가 호출되면 해시가 변경되어 hashchange 이벤트가 트리거되고 기록 라이브러리에서 해당 구독자에게 변경 내용을 알립니다. #🎜🎜##🎜🎜#Summary#🎜🎜##🎜🎜#이 글은 React Router 코어가 의존하는 히스토리 라이브러리에 대한 보다 심층적인 소개를 제공합니다. HTML5의 새로운 히스토리 객체부터 시작하여 히스토리 라이브러리와의 불가분의 관계를 비교하고 hashHistory를 예제로 사용하여 코드의 구현 세부 사항을 자세히 분석합니다. #🎜🎜##🎜🎜#마지막으로 히스토리 라이브러리가 수행한 작업을 검토해 보겠습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#HTML5 히스토리 객체의 개념을 배우고 이를 확장합니다. 기능#🎜🎜##🎜🎜##🎜🎜##🎜🎜# 3가지 유형의 기록 제공: browserHistory, hashHistory, memoryHistory 및 통합 API 유지#🎜🎜##🎜🎜##🎜🎜## 🎜🎜# 게시/구독 기능을 지원합니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜# 점프 차단, 점프 확인, 베이스 이름 등 실용적인 기능을 제공합니다. 🎜🎜##🎜🎜##🎜🎜##🎜🎜#히스토리 라이브러리는 React Router의 핵심 종속성이지만 React 자체에는 종속성이 없습니다. 프로젝트에 기록 조작 시나리오가 있는 경우 이를 프로젝트에 도입할 수도 있습니다. #🎜🎜#

관련 권장 사항:

h5를 사용하여 반응 드래그 앤 드롭 정렬 구성 요소를 구현하는 방법(코드 포함)

HTML5가 여백 상단 축소 문제를 해결하는 방법(코드 포함)

태그와 공통 항목은 무엇입니까? HTML5의 규칙? html5 태그 및 규칙 소개


위 내용은 React Router의 핵심 히스토리 라이브러리에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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