UNPKG

4.9 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 const state = store.getState()
27 let infos = funcGetPageInfo(state, renderProps)
28 if (typeof infos !== 'object') infos = {}
29
30 const {
31 title = '',
32 metas = []
33 } = infos
34
35 if (state.localeId) {
36 if (!metas.some(meta => {
37 if (meta.name === 'koot-locale-id') {
38 meta.content = state.localeId
39 return true
40 }
41 return false
42 })) {
43 metas.push({
44 name: 'koot-locale-id',
45 content: state.localeId
46 })
47 }
48 }
49
50 return {
51 title,
52 metas
53 }
54 }
55
56 class KootPage extends React.Component {
57 // static contextTypes = {
58 // store: PropTypes.object
59 // }
60 static onServerRenderHtmlExtend = ({ htmlTool, store, renderProps = {} }) => {
61 const infos = getInfo(store, renderProps)
62 htmlTool.title = infos.title
63 htmlTool.metas = infos.metas
64 }
65
66 updateInfo() {
67 if (__SERVER__) return
68
69 const infos = getInfo(store, this.props)
70
71 // 替换页面标题
72 document.title = infos.title
73
74 // 替换 metas
75 const head = document.getElementsByTagName('head')[0]
76 if (!Array.isArray(currentMetaTags)) {
77 currentMetaTags = []
78 // 移除所有在 KOOT_METAS 里的 meta 标签
79 // 采用 DOM 操作的初衷:如果使用 innerHTML 的字符串替换方法,浏览器可能会全局重新渲染一次,造成“闪屏”
80 const childNodes = head.childNodes
81 const nodesToRemove = []
82 let meetStart = false
83 let meetEnd = false
84 let i = 0
85 while (!meetEnd && childNodes[i] instanceof Node) {
86 const node = childNodes[i]
87 if (node.nodeType === Node.COMMENT_NODE) {
88 if (node.nodeValue === __KOOT_INJECT_METAS_START__)
89 meetStart = true
90 if (node.nodeValue === __KOOT_INJECT_METAS_END__) {
91 meetEnd = true
92 nodeCommentEnd = node
93 }
94 } else if (meetStart && node.nodeType === Node.ELEMENT_NODE && node.tagName === 'META') {
95 nodesToRemove.push(node)
96 }
97 i++
98 }
99 nodesToRemove.forEach(el => head.removeChild(el))
100 }
101
102 currentMetaTags.forEach(el => {
103 if (el && el.parentNode)
104 el.parentNode.removeChild(el)
105 })
106 currentMetaTags = infos.metas
107 .filter(meta => typeof meta === 'object')
108 .map(meta => {
109 const el = document.createElement('meta')
110 for (var key in meta) {
111 el.setAttribute(key, meta[key])
112 }
113 // el.setAttribute(__KOOT_INJECT_ATTRIBUTE_NAME__, '')
114 if (nodeCommentEnd) {
115 head.insertBefore(el, nodeCommentEnd)
116 } else {
117 head.appendChild(el)
118 }
119 return el
120 })
121 }
122
123 componentDidUpdate(prevProps) {
124 if (typeof prevProps.location === 'object' &&
125 typeof this.props.location === 'object' &&
126 prevProps.location.pathname !== this.props.location.pathname
127 )
128 this.updateInfo()
129 }
130
131 componentDidMount() {
132 if (!everMounted) {
133 everMounted = true
134 return
135 }
136 this.updateInfo()
137 }
138
139 render = () => <WrappedComponent {...this.props} />
140 }
141
142 return hoistStatics(KootPage, WrappedComponent)
143}
144
\No newline at end of file