UNPKG

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