最近開始學習 JavaScript, 趁著 trace 別人寫的 code 來認識基本語法; 本篇就從為了要實做一些動畫效果, 一邊觀察 react-native-animatable 這個模組來一邊學習動畫的操作。
Animation Reference @browniefed
關於動畫功能介紹與舉例皆滿詳盡的一本 GitBook。
ReactJS Component 生命週期
在介紹模組內容之前, 要先補齊一些對元件的基礎觀念。( Reference I、Reference II )
3 Stages
- Mounting:元件正準備要被寫入 DOM
- Updating:元件偵測到狀態的改變準備重新渲染。
- Unmounting:元件正要被從 DOM 中移除。
分別會用 Will、Did 來修飾被觸發的 Callback Function Name, 來代表在這些 Stage 發生前或後的關係。
- getInitialState() / constructor():當物件被調用時此方法會在寫入 DOM 之前被觸發, 通常用來管理狀態的元件可以用這個方法初始化一些資料, constructor 的 naming 比較符合物件導向習慣的用語, 但是在 ES6 才支援。
- componentWillMount:當元件內部的結構處理完畢 準備寫入 DOM 之前觸發。
- componentDidMount(DOMElement rootNode):當元件 被寫入 DOM 之後觸發。
當初始化需要操作 DOM 元素就可以用這個方法。
componentWillReceiveProps(nextProps):已掛載的元件收到新的 props 時被觸發。
在這個方法裡你通常會去比較 this.props 和 nextProps 然後再用 this.setState 去改變狀態。
shouldComponentUpdate(nextProps, nextState):這個函式需要回傳一個布林值, 當元件判斷是否需要更新 DOM 時會被觸發。
你可以在這個方法裡面去比較 this.props、this.state、nextProps、nextState 來決定是否需要更新, 回傳 false 則會跳過此次觸發不更新, 如果你什麼都不回傳預設會當做 false。
componentWillUpdate:例如在上面 shouldComponentUpdate 你回傳了 true, 元件確定要更新了, 在準備更新前這個方法會被觸發。
- componentDidupdate(prevProps, prevState, rootNode):更新後觸發。
Unmounting Stage
- componentWillUnmount():當元件準備要被移除或破壞時觸發。
補充
- 掛載後才能使用的方法
- getDOMNode():使用此方法會傳回一個 DOM 元素物件, 透過這個方法你可以取得一個參考物件直接操作 DOM 節點。
- forceUpdate():任何已掛載的元件, 當你知道元件內部有些狀態已經改變但他不是透過 this.setState() 去修改值的時候可以呼叫這個方法強迫更新。
- componentDidMount() 和 componentDidUpdate() 的 rootNode 參數只是提供你一個比較方便的方式存取 DOM , 實際使用和 this.getDOMNode() 是一樣的。
react-native-animatable 的模組結構很單純, Source Code 的部分只有一個 index.js 檔案, 後面將對 Source Code 的部分拆分成幾個 Section (片段) 來介紹。
Code Section 1
// Transform an object to an array the way react native wants it for transform styles
// { a: x, b: y } => [{ a: x }, { b: y }]
function createKeyedArray(obj) {
return Object.keys(obj).map(key => {
let keyed = {};
keyed[key] = obj[key]; // Copy value
return keyed;
});
}
Goal: { a: x, b: y } => [{ a: x }, { b: y }]
語法複習
- Object.keys(obj) 從 object 中取出所有的鍵值, 物件的屬性構成由 (key, value)
- Array.prototype.map(Callback) 會將當前陣列的每個元素執行 Callback 回傳的結果, 集合成另一個陣列回傳
- => fat arrow function (箭頭函式)
- (param1, param2, …, paramN) => { statements }
- (param1, param2, …, paramN) => expression // 等於 : => { return expression; }
- singleParam => { statements } // 只有一個參數時,
- () => { statements } //若無參數,就一定要加括號:
Code Section 2
// Helper function to calculate transform values, args:
// direction: in|out
// originOrDestination: up|down|left|right
// verticalValue: amplitude for up/down animations
// horizontalValue: amplitude for left/right animations
function getAnimationValueForDirection(direction, originOrDestination, verticalValue, horizontalValue) {
const isVertical = originOrDestination === 'up' || originOrDestination === 'down';
const modifier = (isVertical && direction === 'out' ? -1 : 1) * (originOrDestination === 'down' || originOrDestination === 'left' ? -1 : 1);
return modifier * (isVertical ? verticalValue : horizontalValue);
}
Code Section 3
const TRANSFORM_STYLE_PROPERTIES = [
'rotate',
'rotateX',
'rotateY',
'rotateZ',
'scale',
'scaleX',
'scaleY',
'translateX',
'translateY',
'skewX',
'skewY',
];
// Transforms { translateX: 1 } to { transform: [{ translateX: 1 }]}
function wrapStyleTransforms(style) {
let wrapped = {};
Object.keys(style).forEach(key => {
if (TRANSFORM_STYLE_PROPERTIES.indexOf(key) !== -1) {
if (!wrapped.transform) {
wrapped.transform = []; // Create new array.
}
wrapped.transform.push({
[key]: style[key],
});
} else {
wrapped[key] = style[key];
}
});
return wrapped;
}
Goal: Transforms { translateX: 1, undefined: value } to { transform: [{ translateX: 1 }], undefined: value}
語法複習
- forEach 如同 map 會反覆迭代陣列中的元素, 差別是不會回傳值
// Returns a flattened version of style with only `keys` values.
function getStyleValues(keys, style) {
if (!StyleSheet.flatten) {
throw new Error('StyleSheet.flatten not available, upgrade React Native or polyfill with StyleSheet.flatten = require(\'flattenStyle\');');
}
let values = {};
let flatStyle = Object.assign({}, StyleSheet.flatten(style));
if (flatStyle.transform) {
flatStyle.transform.forEach(transform => {
const key = Object.keys(transform)[0];
flatStyle[key] = transform[key];
});
delete flatStyle.transform;
}
(typeof keys === 'string' ? [keys] : keys).forEach(key => {
values[key] = (key in flatStyle ? flatStyle[key] : getDefaultStyleValue(key));
});
return values;
}
- StyleSheet.flatten Flattens an array of style objects, 將多個 style object 合併成單一有效的 style object (重複的元素會剔除掉無效的) Reference
Object.assign(target, ...sources) 回傳從 source 端複製後的 target object
Object.assign 對於 object 中如果存在其他指向 object 的屬性, 會僅複製到 reference, 造成兩物件的物件屬性如果變化會產生連動關係, 可透過lodash 的 clonedeep 來解決。Reference I、Reference II
Property initializers ES6+ 的寫法之一是透過 static propType、static defaultProps 為 property 建立檢查的機制與預設值
... 擴展運算符 (Spread Operator)
var str = 'hello';
[...str] // ['h','e','l','l','o']