UNPKG

4.56 kBJavaScriptView Raw
1import React from 'react'
2// import PropTypes from 'prop-types'
3// import { store } from 'koot/ReactApp'
4import { store, localeId } from '../'
5import hoistStatics from 'hoist-non-react-statics'
6
7let currentMetaTags
8let everMounted = false
9let nodeCommentEnd
10
11/**
12 * @callback funcGetPageInfo
13 * @param {Object} state 当前 state
14 * @param {Object} renderProps 服务器端渲染时的 props
15 * @returns {Object}
16 */
17
18/**
19 * 修改页面 title 和 meta,可用于同构
20 * @param {funcGetPageInfo} callback
21 */
22export default (funcGetPageInfo) => (WrappedComponent) => {
23 const getInfo = (store, renderProps) => {
24 if (typeof funcGetPageInfo !== 'function') return
25
26 let infos = funcGetPageInfo(store.getState(), renderProps)
27 if (typeof infos !== 'object') infos = {}
28
29 const {
30 title = '',
31 metas = []
32 } = infos
33
34 if (localeId)
35 metas.push({
36 name: 'koot-locale-id',
37 content: localeId
38 })
39
40 return {
41 title,
42 metas
43 }
44 }
45
46 class KootPage extends React.Component {
47 // static contextTypes = {
48 // store: PropTypes.object
49 // }
50 static onServerRenderHtmlExtend = ({ htmlTool, store, renderProps = {} }) => {
51 const infos = getInfo(store, renderProps)
52 htmlTool.title = infos.title
53 htmlTool.metas = infos.metas
54 }
55
56 updateInfo() {
57 if (__SERVER__) return
58
59 const infos = getInfo(store, this.props)
60
61 // 替换页面标题
62 document.title = infos.title
63
64 // 替换 metas
65 const head = document.getElementsByTagName('head')[0]
66 if (!Array.isArray(currentMetaTags)) {
67 currentMetaTags = []
68 // 移除所有在 KOOT_METAS 里的 meta 标签
69 // 采用 DOM 操作的初衷:如果使用 innerHTML 的字符串替换方法,浏览器可能会全局重新渲染一次,造成“闪屏”
70 const childNodes = head.childNodes
71 const nodesToRemove = []
72 let meetStart = false
73 let meetEnd = false
74 let i = 0
75 while (!meetEnd && childNodes[i] instanceof Node) {
76 const node = childNodes[i]
77 if (node.nodeType === Node.COMMENT_NODE) {
78 if (node.nodeValue === __KOOT_INJECT_METAS_START__)
79 meetStart = true
80 if (node.nodeValue === __KOOT_INJECT_METAS_END__) {
81 meetEnd = true
82 nodeCommentEnd = node
83 }
84 } else if (meetStart && node.nodeType === Node.ELEMENT_NODE && node.tagName === 'META') {
85 nodesToRemove.push(node)
86 }
87 i++
88 }
89 nodesToRemove.forEach(el => head.removeChild(el))
90 }
91
92 currentMetaTags.forEach(el => {
93 if (el && el.parentNode)
94 el.parentNode.removeChild(el)
95 })
96 currentMetaTags = infos.metas
97 .filter(meta => typeof meta === 'object')
98 .map(meta => {
99 const el = document.createElement('meta')
100 for (var key in meta) {
101 el.setAttribute(key, meta[key])
102 }
103 // el.setAttribute(__KOOT_INJECT_ATTRIBUTE_NAME__, '')
104 if (nodeCommentEnd) {
105 head.insertBefore(el, nodeCommentEnd)
106 } else {
107 head.appendChild(el)
108 }
109 return el
110 })
111 }
112
113 componentDidUpdate(prevProps) {
114 if (typeof prevProps.location === 'object' &&
115 typeof this.props.location === 'object' &&
116 prevProps.location.pathname !== this.props.location.pathname
117 )
118 this.updateInfo()
119 }
120
121 componentDidMount() {
122 if (!everMounted) {
123 everMounted = true
124 return
125 }
126 this.updateInfo()
127 }
128
129 render = () => <WrappedComponent {...this.props} />
130 }
131
132 return hoistStatics(KootPage, WrappedComponent)
133}
134
\No newline at end of file