UNPKG

7.56 kBJavaScriptView Raw
1import {
2 internal_safe_get as safeGet,
3 internal_safe_set as safeSet,
4 commitAttachRef,
5 Current,
6 invokeEffects,
7 getIsUsingDiff
8} from '@tarojs/taro'
9import { componentTrigger } from './create-component'
10import { shakeFnFromObject, isEmptyObject, diffObjToPath, isFunction, isUndefined, isArray } from './util'
11import PropTypes from 'prop-types'
12import { enqueueRender } from './render-queue'
13
14const isDEV = typeof process === 'undefined' ||
15 !process.env ||
16 process.env.NODE_ENV !== 'production'
17
18function hasNewLifecycle (component) {
19 const { constructor: { getDerivedStateFromProps }, getSnapshotBeforeUpdate } = component
20 return isFunction(getDerivedStateFromProps) || isFunction(getSnapshotBeforeUpdate)
21}
22
23function callGetDerivedStateFromProps (component, props, state) {
24 const { getDerivedStateFromProps } = component.constructor
25 let newState
26 if (isFunction(getDerivedStateFromProps)) {
27 const partialState = getDerivedStateFromProps(props, state)
28 if (!isUndefined(partialState)) {
29 newState = Object.assign({}, state, partialState)
30 } else {
31 console.warn('getDerivedStateFromProps 没有返回任何内容,这个生命周期必须返回 null 或一个新对象。')
32 }
33 }
34 return newState
35}
36
37function callGetSnapshotBeforeUpdate (component, props, state) {
38 const { getSnapshotBeforeUpdate } = component
39 let snapshot
40 if (isFunction(getSnapshotBeforeUpdate)) {
41 snapshot = getSnapshotBeforeUpdate.call(component, props, state)
42 }
43 return snapshot
44}
45
46export function updateComponent (component) {
47 const { props, __propTypes } = component
48 if (isDEV && __propTypes) {
49 let componentName = component.constructor.name
50 if (isUndefined(componentName)) {
51 const names = component.constructor.toString().match(/^function\s*([^\s(]+)/)
52 componentName = isArray(names) ? names[0] : 'Component'
53 }
54 PropTypes.checkPropTypes(__propTypes, props, 'prop', componentName)
55 }
56 const prevProps = component.prevProps || props
57 component.props = prevProps
58 if (component.__mounted && component._unsafeCallUpdate === true && !hasNewLifecycle(component) && component.componentWillReceiveProps) {
59 component._disable = true
60 component.componentWillReceiveProps(props)
61 component._disable = false
62 }
63 let state = component.getState()
64
65 const prevState = component.prevState || state
66
67 const stateFromProps = callGetDerivedStateFromProps(component, props, state)
68
69 if (!isUndefined(stateFromProps)) {
70 state = stateFromProps
71 }
72
73 let skip = false
74 if (component.__mounted) {
75 if (typeof component.shouldComponentUpdate === 'function' &&
76 !component._isForceUpdate &&
77 component.shouldComponentUpdate(props, state) === false) {
78 skip = true
79 } else if (!hasNewLifecycle(component) && isFunction(component.componentWillUpdate)) {
80 component.componentWillUpdate(props, state)
81 }
82 }
83
84 component.props = props
85 component.state = state
86 component._dirty = false
87 component._isForceUpdate = false
88 if (!skip) {
89 doUpdate(component, prevProps, prevState)
90 }
91 component.prevProps = component.props
92 component.prevState = component.state
93}
94
95function injectContextType (component) {
96 const ctxType = component.constructor.contextType
97 if (ctxType) {
98 const context = ctxType.context
99 const emitter = context.emitter
100 if (emitter === null) {
101 component.context = context._defaultValue
102 return
103 }
104 if (!component._hasContext) {
105 component._hasContext = true
106 emitter.on(_ => enqueueRender(component))
107 }
108 component.context = emitter.value
109 }
110}
111
112export function mountComponent (component) {
113 const { props } = component
114 // 在willMount前执行构造函数的副本
115 if (!component.__componentWillMountTriggered) {
116 component._constructor && component._constructor(props)
117 }
118
119 const newState = callGetDerivedStateFromProps(component, props, component.state)
120
121 if (!isUndefined(newState)) {
122 component.state = newState
123 }
124
125 component._dirty = false
126 component._disable = false
127 component._isForceUpdate = false
128 if (!component.__componentWillMountTriggered) {
129 component.__componentWillMountTriggered = true
130 if (!hasNewLifecycle(component)) {
131 componentTrigger(component, 'componentWillMount')
132 }
133 }
134 doUpdate(component, props, component.state)
135 component.prevProps = component.props
136 component.prevState = component.state
137}
138
139function doUpdate (component, prevProps, prevState) {
140 const { state, props = {} } = component
141 let data = state || {}
142 if (component._createData) {
143 if (component.__isReady) {
144 injectContextType(component)
145 Current.current = component
146 Current.index = 0
147 invokeEffects(component, true)
148 }
149 data = component._createData(state, props) || data
150 if (component.__isReady) {
151 Current.current = null
152 }
153 }
154
155 data = Object.assign({}, props, data)
156 if (component.$usedState && component.$usedState.length) {
157 const _data = {}
158 component.$usedState.forEach(key => {
159 let val = safeGet(data, key)
160 if (typeof val === 'undefined') {
161 return
162 }
163 if (typeof val === 'object') {
164 if (isEmptyObject(val)) return safeSet(_data, key, val)
165
166 val = shakeFnFromObject(val)
167 // 避免筛选完 Fn 后产生了空对象还去渲染
168 if (!isEmptyObject(val)) safeSet(_data, key, val)
169 } else {
170 safeSet(_data, key, val)
171 }
172 })
173 data = _data
174 }
175 data['$taroCompReady'] = true
176
177 const dataDiff = getIsUsingDiff() ? diffObjToPath(data, component.$scope.data) : data
178 const __mounted = component.__mounted
179 let snapshot
180 if (__mounted) {
181 snapshot = callGetSnapshotBeforeUpdate(component, prevProps, prevState)
182 }
183 // 每次 setData 都独立生成一个 callback 数组
184 let cbs = []
185 if (component._pendingCallbacks && component._pendingCallbacks.length) {
186 cbs = component._pendingCallbacks
187 component._pendingCallbacks = []
188 }
189
190 const cb = function () {
191 invokeEffects(component)
192 if (__mounted) {
193 if (component['$$refs'] && component['$$refs'].length > 0) {
194 component['$$refs'].forEach(ref => {
195 // 只有 component 类型能做判断。因为 querySelector 每次调用都一定返回 nodeRefs,无法得知 dom 类型的挂载状态。
196 if (ref.type !== 'component') return
197
198 let target = component.$scope.selectComponent(`#${ref.id}`)
199 target = target ? (target.$component || target) : null
200
201 const prevRef = ref.target
202 if (target !== prevRef) {
203 commitAttachRef(ref, target, component, component.refs)
204 ref.target = target
205 }
206 })
207 }
208
209 if (component['$$hasLoopRef']) {
210 Current.current = component
211 Current.index = 0
212 component._disableEffect = true
213 component._createData(component.state, component.props, true)
214 component._disableEffect = false
215 Current.current = null
216 }
217
218 if (typeof component.componentDidUpdate === 'function') {
219 component.componentDidUpdate(prevProps, prevState, snapshot)
220 }
221 }
222
223 if (!__mounted) {
224 component.__mounted = true
225 componentTrigger(component, 'componentDidMount')
226 }
227
228 if (cbs.length) {
229 let i = cbs.length
230 while (--i >= 0) {
231 typeof cbs[i] === 'function' && cbs[i].call(component)
232 }
233 }
234 }
235 if (Object.keys(dataDiff).length === 0) {
236 cb()
237 invokeEffects(component)
238 } else {
239 component.$scope.setData(dataDiff, cb)
240 }
241}