UNPKG

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