UNPKG

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