UNPKG

11.5 kBJavaScriptView Raw
1/* global __KOOT_SSR__:false */
2
3import { getStore } from '../'
4
5import React from 'react'
6import { connect } from 'react-redux'
7// import { hot } from 'react-hot-loader'
8// import PropTypes from 'prop-types'
9import hoistStatics from 'hoist-non-react-statics'
10
11//
12
13import {
14 fromServerProps as getRenderPropsFromServerProps,
15 fromComponentProps as getRenderPropsFromComponentProps
16} from './get-render-props'
17import {
18 append as appendStyle,
19 remove as removeStyle,
20 // StyleMapContext,
21} from './styles'
22import clientUpdatePageInfo from './client-update-page-info'
23
24//
25
26// 是否已挂载了组件
27let everMounted = false
28// const defaultPageInfo = {
29// title: '',
30// metas: []
31// }
32const styleMap = {}
33
34/**
35 * @type {Number}
36 * _开发环境_
37 * _服务器_
38 * 使用该高阶组件的次数
39 */
40let devSSRConnectIndex = 0
41
42/**
43 * 获取数据
44 * @callback callbackFetchData
45 * @param {Object} state 当前 state
46 * @param {Object} renderProps 封装的同构 props
47 * @param {Function} dispatch Redux dispatch
48 * @returns {Promise}
49 */
50
51/**
52 * 判断数据是否准备好
53 * @callback callbackCheckLoaded
54 * @param {Object} state 当前 state
55 * @param {Object} renderProps 封装的同构 props
56 * @returns {Boolean}
57 */
58
59/**
60 * 获取页面信息
61 * @callback callbackGetPageInfo
62 * @param {Object} state 当前 state
63 * @param {Object} renderProps 封装的同构 props
64 * @returns {Object}
65 */
66
67/**
68 * 获取同构数据的执行方法
69 * @param {Object} store
70 * @param {Object} props renderProps
71 * @returns {Promise}
72 */
73const doFetchData = (store, renderProps, dataFetch) => {
74 const result = dataFetch(store.getState(), renderProps, store.dispatch)
75 // if (result === true) {
76 // isDataPreloaded = true
77 // return new Promise(resolve => resolve())
78 // }
79 if (Array.isArray(result))
80 return Promise.all(result)
81 if (result instanceof Promise)
82 return result
83 return new Promise(resolve => resolve(result))
84}
85
86/**
87 * 更新页面信息
88 * @param {Object} store
89 * @param {Object} props renderProps
90 * @returns {Object} infos
91 * @returns {String} infos.title
92 * @returns {Array} infos.metas
93 */
94const doPageinfo = (store, props, pageinfo) => {
95 const defaultPageInfo = {
96 title: '',
97 metas: []
98 }
99
100 if (typeof pageinfo !== 'function' && typeof pageinfo !== 'object')
101 return defaultPageInfo
102
103 const state = store.getState()
104 const infos = (() => {
105 if (typeof pageinfo === 'object')
106 return pageinfo
107 const infos = pageinfo(state, props)
108 if (typeof infos !== 'object')
109 return defaultPageInfo
110 return infos
111 })()
112
113 const {
114 title = defaultPageInfo.title,
115 metas = defaultPageInfo.metas
116 } = infos
117
118 if (state.localeId) {
119 if (!metas.some(meta => {
120 if (meta.name === 'koot-locale-id') {
121 meta.content = state.localeId
122 return true
123 }
124 return false
125 })) {
126 metas.push({
127 name: 'koot-locale-id',
128 content: state.localeId
129 })
130 }
131 }
132
133 return {
134 title,
135 metas
136 }
137}
138
139// console.log((typeof store === 'undefined' ? `\x1b[31m×\x1b[0m` : `\x1b[32m√\x1b[0m`) + ' store in [HOC] extend')
140/**
141 * 高阶组件/组件装饰器:组件扩展
142 * @param {Object} options 选项
143 * @param {Boolean|Function} [options.connect] react-redux 的 connect() 的参数。如果为 true,表示使用 connect(),但不连接任何数据
144 * @param {Object|callbackGetPageInfo} [options.pageinfo]
145 * @param {Object} [options.data] 同构数据相关
146 * @param {callbackFetchData} [options.data.fetch]
147 * @param {callbackCheckLoaded} [options.data.check]
148 * @param {Object} [options.styles] 组件 CSS 结果
149 * @returns {Function} 封装好的 React 组件
150 */
151export default (options = {}) => (WrappedComponent) => {
152 // console.log((typeof store === 'undefined' ? `\x1b[31m×\x1b[0m` : `\x1b[32m√\x1b[0m`) + ' store in [HOC] extend run')
153
154 const {
155 connect: _connect = false,
156 pageinfo,
157 data: {
158 fetch: _dataFetch,
159 check: dataCheck,
160 } = {},
161 styles: _styles,
162 // ttt
163 // hot: _hot = true,
164 // name
165 } = options
166
167 // console.log('extend hoc run', { name, LocaleId })
168
169 // 样式相关
170
171 /** @type {Object} 经过 koot-css-loader 处理后的 css 文件的结果对象 */
172 const styles = (!Array.isArray(_styles) ? [_styles] : _styles).filter(obj => (
173 typeof obj === 'object' && typeof obj.wrapper === 'string'
174 ))
175
176 /** @type {Boolean} 是否有上述结果对象 */
177 const hasStyles = (
178 Array.isArray(styles) &&
179 styles.length > 0
180 )
181 // console.log({ ttt, hasStyles, styles })
182
183 // 同构数据相关
184
185 /** @type {Boolean} 同构数据是否已经获取成功 */
186 // let isDataPreloaded = false
187
188 /** @type {Function} 获取同构数据 */
189 const dataFetch = typeof options.data === 'function' || Array.isArray(options.data)
190 ? options.data
191 : (typeof _dataFetch === 'function' || Array.isArray(_dataFetch) ? _dataFetch : undefined)
192
193 // 装饰组件
194
195 class KootReactComponent extends React.Component {
196 static onServerRenderHtmlExtend = ({ store, renderProps = {} }) => {
197 const {
198 title,
199 metas
200 } = doPageinfo(store, getRenderPropsFromServerProps(renderProps), pageinfo)
201 return { title, metas }
202 }
203
204 //
205
206 // static contextType = StyleMapContext
207
208 //
209
210 clientUpdatePageInfo() {
211 if (typeof pageinfo !== 'function' && typeof pageinfo !== 'object')
212 return
213
214 const {
215 title,
216 metas
217 } = doPageinfo(getStore(), getRenderPropsFromComponentProps(this.props), pageinfo)
218 clientUpdatePageInfo(title, metas)
219 }
220
221 //
222
223 state = {
224 loaded: typeof dataCheck === 'function'
225 ? dataCheck(getStore().getState(), getRenderPropsFromComponentProps(this.props))
226 : undefined
227 ,
228 }
229 mounted = false
230 kootClassNames = []
231
232 //
233
234 constructor(props/*, context*/) {
235 super(props/*, context*/)
236
237 if (hasStyles) {
238 this.kootClassNames = styles.map(obj => obj.wrapper)
239 appendStyle(this.getStyleMap(/*context*/), styles)
240 // console.log('----------')
241 // console.log('styles', styles)
242 // console.log('theStyles', theStyles)
243 // console.log('this.classNameWrapper', this.classNameWrapper)
244 // console.log('----------')
245 }
246 }
247
248 /**
249 * 获取 styleMap
250 * - 服务器端: 返回全局常量中的对照表
251 * - 客户端: 直接返回本文件内的 styleMap
252 */
253 getStyleMap(/*context*/) {
254 // console.log('extend', { LocaleId })
255 if (__SERVER__) {
256 if (__DEV__)
257 return global.__KOOT_SSR__.styleMap
258 if (typeof __KOOT_SSR__ === 'object')
259 return __KOOT_SSR__.styleMap
260 }
261 return styleMap
262 // return context
263 }
264
265 //
266
267 componentDidUpdate(prevProps) {
268 if (typeof prevProps.location === 'object' &&
269 typeof this.props.location === 'object' &&
270 prevProps.location.pathname !== this.props.location.pathname
271 )
272 this.clientUpdatePageInfo()
273 }
274
275 componentDidMount() {
276 this.mounted = true
277
278 if (!this.state.loaded && typeof dataFetch !== 'undefined') {
279 doFetchData(getStore(), getRenderPropsFromComponentProps(this.props), dataFetch)
280 .then(() => {
281 if (!this.mounted) return
282 this.setState({
283 loaded: true,
284 })
285 })
286 }
287
288 this.clientUpdatePageInfo()
289 if (everMounted) {
290 } else {
291 everMounted = true
292 }
293 }
294
295 componentWillUnmount() {
296 this.mounted = false
297 if (hasStyles) {
298 removeStyle(this.getStyleMap(/*this.context*/), styles)
299 }
300 }
301
302 //
303
304 render = () => {
305 // console.log('styles', styles)
306 // console.log('this', this)
307 // console.log('this.kootClassNames', this.kootClassNames)
308 // console.log('this.props.className', this.props.className)
309
310 if (__CLIENT__ && this.kootClassNames instanceof HTMLElement) {
311 // console.log(this.kootClassNames)
312 this.kootClassNames = [this.kootClassNames.getAttribute('id')]
313 }
314
315 const props = Object.assign({}, this.props, {
316 className: this.kootClassNames.concat(this.props.className).join(' ').trim(),
317 "data-class-name": this.kootClassNames.join(' ').trim(),
318 })
319
320 // if (__SERVER__) console.log('extender this.state.loaded', this.state.loaded)
321 if (typeof dataFetch !== 'undefined' && typeof dataCheck === 'function')
322 props.loaded = this.state.loaded
323
324 return <WrappedComponent {...props} />
325 }
326 }
327
328 if (typeof dataFetch !== 'undefined') {
329 KootReactComponent.onServerRenderStoreExtend = ({ store, renderProps }) => {
330 if (typeof dataFetch === 'undefined')
331 return new Promise(resolve => resolve())
332 // console.log('onServerRenderStoreExtend')
333 return doFetchData(store, getRenderPropsFromServerProps(renderProps), dataFetch)
334 }
335 }
336
337 // if (_hot && __DEV__ && __CLIENT__) {
338 // const { hot, setConfig } = require('react-hot-loader')
339 // setConfig({ logLevel: 'debug' })
340 // KootComponent = hot(module)(KootComponent)
341 // }
342
343 let KootComponent = hoistStatics(KootReactComponent, WrappedComponent)
344
345 // if (typeof styles === 'object' &&
346 // typeof styles.wrapper === 'string'
347 // ) {
348 // KootComponent = ImportStyle(styles)(KootComponent)
349 // }
350
351 if (_connect === true) {
352 KootComponent = connect(() => ({}))(KootComponent)
353 } else if (typeof _connect === 'function') {
354 KootComponent = connect(_connect)(KootComponent)
355 } else if (Array.isArray(_connect)) {
356 KootComponent = connect(..._connect)(KootComponent)
357 }
358
359 /**
360 * _服务器端_
361 * 将组件注册到同构渲染对象中
362 */
363 if (__SERVER__) {
364 if (__DEV__) KootComponent.id = devSSRConnectIndex++
365 const {
366 connectedComponents = []
367 } = __DEV__ ? global.__KOOT_SSR__ : __KOOT_SSR__
368 connectedComponents.push(KootComponent)
369 }
370
371 return KootComponent
372}
373
\No newline at end of file