历史渊源
喂,不能否认,React Router那玩意儿中的History库确实有用。好几个版本换下来,除了基础功能,其他没啥变化。咱们先说说用jQuery那段日子,那时候网络太卡顿,SPA技术开始火爆起来。怎样实现网页刷新后内容不变?其实就靠#号,每次刷新的时候改下#号就行了。浏览器看到网址或#号变了,就会把新地址自动塞进History对象。这History里有个state数组,就是记录你访问过哪些页面的。所以当你按”后退”或”前进”时,其实是用到了History对象的forward/back方法,找到现在状态,再转到相应页面去的。
核心功能
你听过 pushState 和 replaceState 这两个东西吗?它们都是 history 里的好帮手~有了它们,你无需更改网址,就能轻松地更换页面的显示内容!像不像当年咱们喜欢用的 hashHistory?你只需要研究一下代码,就会发现它们其实就像是引导性的订阅列表。只要哈希值一变,它立马会启动相应的处理程序。现在,懂了?
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变更事件 };
主动更新
const history = { length, // 属性,history中记录的state的数量 state, // 属性,pushState和replaceState时传入的对象 back, // 方法,后退 forward, // 方法,前进 go, // 方法,前进或后退n个记录 pushState, // 方法,导航到新的路由,并记录在history中 replaceState // 方法,替换掉当前记录在history中的路由信息 } // 订阅history变更事件 window.onpopstate = function (event) { ... }
说到改网页,比如向上或向下滑动页面这些动作,就得用HTML5里的history对象这个小能手。你可以试试按下window.history.go,这样hash值就会改变,hashchange事件也跟着启动起来。然后,history库就会通知他的小伙伴们。
// 构造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); }; ... }
深入分析
这玩意儿可厉害了!让我们聊聊HTML5新出的history对象,那些跟浏览器历史纪录有关的话题。比如hashHistory,我给你说说到底是咋回事。而且还有个神奇功能,就是用它发布和订阅!每当你的浏览器历史记录有变化时,订阅的函数就会立刻启动!
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)); }; ... }
关系解析
记住咯,History其实跟React没有啥子直接关系,它只是在React Router这个大家庭里待着而已,和 React没有明确的链接!
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);
评论0