顯示具有 React 標籤的文章。 顯示所有文章
顯示具有 React 標籤的文章。 顯示所有文章

2023年12月3日 星期日

我受夠了react的hook,我想寫vue啊~

前言

你是否有跟我一樣的困擾呢,覺得react class component的setState寫法相當繁瑣,覺得react hook的的queue也不夠直覺,為什麼不能直接update state然後畫面就自動render需要透過一個setter,然後資料異動還不是同步的,也不是異步,竟然是透過queue......這是我一直以來的心聲。我今天沒有要戰,純粹是就職以來的一些小心得,作為一套老牌框架,我承認他的市佔率,但我覺得如果以便利性讓我挑選,我一定不會選react,但礙於現有的社群要他大改幾乎是不太可能,就算不論三大框架現在市場上也充斥著太多套創新的框架(qwik, svelte, solid, astro....),所以要用一個形容詞來形容這套框架只能說它“過時”了,but!最可怕的but來了,公司就愛用嘛~所以有沒有什麼辦法?

老實說我很驚訝竟然查不到相關的資料,還是大家都超滿意react,沒遇到什麼奇怪的坑,我是異類,總之~辦法還是有的,首先我在腦海中搜索想到了奶綠茶有介紹過preact/signal,奶綠大大事我很喜歡的一位工程師,我大學時還買過他的書,不過那是另外一個故事了

內文

preact跟react是不一樣的框架,但其中的api友87%像,signal使用起來就像是vue裡面的ref(太詳細的內容自己去看原始碼),然後看網誌說明signal無法直接使用在react,特別是底層是vite打包的狀況下(該死)

所以不得已我又開始尋找其他法,後來發現preact/signal有出preact/react-signal特別優化的版本,這樣應該是妥了吧,官方都直接出優化了,直接使用在公司專案結果爆出getSnapshop is undefined......我去查了一下原始碼也一無所獲,歸功於react亂七八糟......抱歉我不該戰,是莫名其妙...抱歉,是我孤陋寡聞不得而知的底層運作機制,所以我放棄了這麼一條路

難道我就該止步於此繼續使用這該死又難寫......抱歉我不該戰,我要繼續使用這我用了很痛苦的框架嗎?當然不是,既然連官方都靠不住,那就只能自己來寫了,一開始我嘗試使用Object.defineProperty去寫(沒錯就是vue2那套,想看原始碼也附註解在裡面),反正所有東西都丟到value下面就好了吧,但也發生了跟vue2一樣的問題,沒有註冊過的屬性就不會reactive,但如果使用Proxy怕太新UC那些奇怪的瀏覽器不支援

沒錯公司要求產品要支援UC,講到這裡我想花一點篇幅抱怨一下,建議看到這裡的工程師,珍惜生命,遠離UC,下次面試記得要問主管公司有沒有要求要支援UC,有人說UC就像手幾板的ie我覺得他說錯了,他比ie還不如!不僅沒辦法debug,之前發生一次白屏,在我一行一行除錯,最後問題竟然是html排序它不滿意,不是什麼沒有加上</>這種低級錯誤喔,就是單純排序它不滿意,當時的狀況就類似以下:
<div>
    hello
    <div>hello2</div>
<div>
然後我把它改成
<div>hello<div>
<div>hello2</div>
就正常了.....我還以為我又用什麼新的寫法或物件戳到它的G點,Fuck!這我真的沒辦法誒!!

好拉,工作還是要繼續下去,但這還在研發階段之不支援我就先不考慮,之後不支援大不了不用!所以你以為我就乖乖刻輪子嗎?當然沒有,我想到既然底層都是Proxy那我用signal就好拉,那畫面不會reactive的問題怎麼處理?奶綠茶大大的網誌有提到react18有出一個新的hook是useSyncExternalStore 這個hook可以控制畫面要不要渲染,網誌裡面有提到大大如何包裝這個function,我試著去改寫但我一直嘗試不成功,因為我一直把變數放在function內,所以在rerender時function會重跑一遍,所以變數每次都會被重置(Fuck我就說寫法很不直覺吧),當我想到這問題時已經好幾天過去了,如果不想變數被重置,那就使用原生的hook來解決吧,最後產出來的code就如下

沒錯,前面說這麼多我只是想要抱怨,最終結果在下面,大家如果為了自己身體著想,可以直接拿去使用

我模擬出一些比較常用的hook,其他我也很少用就不做了,畢竟這就像那些砲轟我的網友說的,就是vue皮react骨,我也沒有不承認這件事,但更新資料畫面就會自動更新就是爽,也不用在那邊setState後還要在useEffect等狀態更新才能在function裡面執行我想執行的內容,這寫法真的很蠢......抱歉我不該戰,這寫法真的很不直覺

好吧廢話不多說直接上程式碼

import { useReactive } from "@reactivedata/react"
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react"
import { signal, effect, computed as preactComputed } from "@preact/signals-core"

export const onMounted = (fn) => {
useEffect(() => {
fn()
}, [])
}
export const onUnmounted = (fn) => {
useEffect(() => {
return () => {
fn()
}
}, [])
}

export const ref = (initValue) => {
const valueRef = useRef(initValue)
const versionRef = useRef(0)
function createStore(signal) {
let onChangeNotifyReact
const unsubscribe = effect(() => {
valueRef.current = signal.value
versionRef.current++
onChangeNotifyReact?.()
})
return {
subscribe(listener) {
onChangeNotifyReact = listener
return () => {
unsubscribe()
}
},
getSnapshop() {
return versionRef.current
},
}
}
const res = signal(valueRef.current)
const store = useMemo(() => {
return createStore(res)
}, [res])
useSyncExternalStore(store.subscribe, store.getSnapshop)
return res
// const _value = useRef(initValue)
// const [ref, setRef] = useState({ value: initValue })
// Object.defineProperty(ref, "value", {
// get() {
// return _value.current
// },
// set(newValue) {
// _value.current = newValue
// setRef({ value: newValue })
// },
// })
// return ref
}
export const reactive = useReactive
export const computed = (computeFn) => {
const res = preactComputed(computeFn)
function createStore(res) {
let onChangeNotifyReact
const unsubscribe = effect(() => {
res.value
onChangeNotifyReact?.()
})
return {
subscribe(listener) {
onChangeNotifyReact = listener
return () => {
unsubscribe()
}
},
getSnapshop() {
return res.value
},
}
}
const store = useMemo(() => {
return createStore(res)
}, [res])
useSyncExternalStore(store.subscribe, store.getSnapshop)
return res.value
}

export const watch = (sSatcher, fn) => {
const watcher = typeof sSatcher === "object" ? sSatcher : [sSatcher]
const value = useMemo(
() =>
watcher.map((x) => {
return typeof x == "function" ? x() : x
}),
[watcher]
)
const ref = useRef(value)
if (value.toString() !== ref.current.toString()) {
ref.current = value
}
useEffect(fn, ref.current)
}

export { effect }

這樣onMounted, onUnmounted也解決了useEffect裡面不能放async的function,那警告看了真的很礙眼,支援就不要吵,不支援就直接抱錯,在那邊吵什麼吵,然後為什麼不多包一層就好,我也不知道,可能礙於什麼東方神秘力量

reactive就直接用別人寫好的吧

watch就是watch

computed這麼好用的功能怎麼能不實現(但我沒實現getter、setter,沒錯因為我不常用)

ref就用useRef去解決變數被重置問題,我們來比較一下差別
// normal way
const [value, setValue] = useState(0)
setValue(1)
console.log(value) // 0 wtf!
useEffect(() => {
    console.log(value) // 1
}, [value])

// better way
const [value, setValue] = useState(0)
const newValue = 1
setValue(newValue)
console.log(newValue) // 1 .....why so troublesome?

// new way
const state = ref(0)
state.value++
console.log(state.value) // 1
// 484很直覺!484很爽!484很好寫


後記

最重要UC, QQ這些中國大牌瀏覽器到底有沒有支援Proxy呢~~答案是有的,至少上線到現在沒有傳出什麼災情
謎之聲:連Promise.race跟any都可以拔掉,竟然支援Proxy,真是可喜可賀~
不過想想也是拉,現在vue3都用Proxy寫了,如此泱泱大國總不會想跟自國工程師對著幹吧!

最後,我沒有實際測過使用起來的效能,我相信應該不會太好,畢竟有內容更新畫面就要重新渲染一次,原本react18 queue的優化都沒用了,但我才不管,開發體驗最重要!!事實上,你如果注重效能就不會選擇react框架了,那這篇就先到這裡拉,希望大家都可以準時下班,see u later~

12/18更新

computed並非是及時的,useSyncExternalStore只用來更新畫面,所以如果要用在邏輯演算中,用useMemo效果可能比較好,之後再來思考看有沒有機會debug,下面有個使用範例,這真令人有點難過

const users = [{ birthday: 2020-01-01}, {birthday:"2022-02-02"}]

let index = 0

const user = computed(() => users[index].birthday) 

console.log(user.birthday) // 2020-01-01

index = 1

console.log(user.birthday) // 2020-01-01 來不及更新


2023年9月13日 星期三

react-router一般加載與懶加載

 在原先規劃系統設計是去讀src/pages/裡面有index.jsx?的頁面,搞得跟next.js類似

事實上這樣也不難,在vite.config.js這樣寫就好

import { defineConfig } from "vite"
import { GlobSync } from "glob"

export default defineConfig(({ mode }) => {
return {
......
define: {
__PAGES_ROUTES__: GlobSync("src/pages/**/index.{js,jsx}").found.map((x) => x.replace(/src\/pages\/(.*)\/index.jsx?/, "$1")),
},
}
})


 import loadable from "@loadable/component"
使用loadable進行懶加載
 ......
        __PAGES_ROUTES__.map((route, i) => {
const Component = loadable(() => import(`./pages/${route}`), {
     fallback: <LayoutPage center={() => ""} />,
})
return <Route path={route} key={i} element={<Component />} />
})

但今天收到一個難題需求,老闆希望把懶加載拔掉,減短切換頁的等待時間,因為不可能把Component在後端讀好傳到前端來,所以上面那方法就作廢,但總不會要我把上百個Component import進來吧,後來想到vite有附glob給我,稍微研究一下改成以下

const modules = import.meta.glob("./pages/**/index.{js,jsx}", {
import: "default",
eager: true,
})
......
    Object.keys(modules).map((route, i) => {
const path = route.replace(/\.\/pages\/(.*)\/index.jsx?/, "$1")
const Component = modules[route]
return <Route path={path} key={i} element={<Component />} />
})}

這樣改唯一的問題是,yarn dev第一次進入頁面等待時間會比較久一點,之後改動要等編譯時間也會久一點。當然在build的客戶端是沒有差的,是開發上的爽度差別而已
補充:用判斷讓import.meta.glob不要執行也沒用,他是屬於編譯層級的,所以一定會跑,要快只能註解掉
然後不能有在./pages/...../index.js是沒有export default的喔,不然會爆掉

總之就是這樣拉,打完收工

react-router動畫切換

 我挺訝異vue-router原生支援的動畫換頁效果,在react-router6竟然沒有支援,官網老實說也寫得不是很清楚,畢竟這版本目前來說還很新,也沒有別人寫好的套件可以直接使用,只好上網爬文怎麼實現

基本上就是參考了這個網站(https://dev.to/fazliddin04/react-router-v6-animated-transitions-diy-3e6l)把最終效果實踐出來

正文開始

// src/style/route-animate.module.scss
.fadeIn {
animation: 0.4s fadeIn forwards;
}

.fadeOut {
animation: 0.2s fadeOut forwards;
}

@keyframes fadeIn {
from {
opacity: 0.6;
transform: translate(-3rem, 0px);
}
to {
opacity: 1;
transform: translate(0px, 0px);
}
}

@keyframes fadeOut {
from {
opacity: 1;
transform: translate(0px, 0px);
}
to {
opacity: 0;
transform: translate(-3rem, 0px);
}
}

// src/App.jsx
import route_styles from "./style/route-animate.module.scss"
......
const RoutesWithAnimate = () => {
const animateEnum = {
fadeIn: "fadeIn",
fadeOut: "fadeOut",
}
const location = useLocation()
const [displayLocation, setDisplayLocation] = useState(location)
const [transitionStage, setTransistionStage] = useState(animateEnum.fadeIn)
useEffect(() => {
    // 防止有變化query的頁面但route沒有變化
if (location.pathname !== displayLocation.pathname) {
setTransistionStage(animateEnum.fadeOut)
} else if (location !== displayLocation) {
setDisplayLocation(location)
}
}, [location, displayLocation])


return (
<div
className={classNames("min-h-full", route_styles[transitionStage])}
onAnimationEnd={() => {
if (transitionStage === animateEnum.fadeOut) {
setTransistionStage(animateEnum.fadeIn)
setDisplayLocation(location)
}
}}
>
<Routes location={displayLocation}>
<Route path={"/home"} element={<Home />} />
        <Route path={"/about"} element={<About />} />
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
</div>
)
}

藉由把location暫存放入Routes裡面,利用時間差做到動畫效果,但終究沒辦法同時存在兩個location,所以沒辦法做到同時兩個route畫面出現在畫面上的效果,類似輪動這樣,這可能需要靠htmlToCanvas達成類似的效果或是等其他人出套件了,總之專案做到這裡就被打掉了....沒錯你沒看錯,被打掉了,因為有人覺得淡入淡出很醜,好吧就這樣吧,做個紀錄,紀錄自己曾經做過。

踩過的小坑
if (location !== displayLocation)

參考的網站判斷原來長這樣,但是這會造成一個問題,如果只是query切換沒有切換pathname,然後畫面中假設彈窗顯示,然後彈窗會去取query的值,這時因為Routes的location還沒有轉換,所以取到的query會是舊的。

例如
router變化:/home?id=1 => /home?id=2
希望達成效果:/home?id=1 => 開始淡出 => 彈窗抓取qeury: id = 2 => 開始淡入
實際執行狀況:/home?id=1 => 開始淡出 => 彈窗抓取qeury: id = 1 => 淡出跑完(setDisplayLocation) => /home?id=2 => 開始淡入 => 淡入跑完

先不要管為什麼系統這樣設計,總之~就是這樣拉~

2016年10月21日 星期五

React入門筆記

必須說小編實在不喜歡React的撰寫方式跟風格,畢竟一開始吃了不少鱉。不過既然我學習過我還是簡易做個筆記吧。
Ps.本篇網誌是有段時間前寫的。

有人會問既然小編不喜歡React,但為什麼還是要學呢?一切都是因為……
網路上工作職缺打開,每個都要React & Angular開發經驗者佳,WTF!! 所以今天你只需要知道,你學會了 =  More Money!! 如何~有沒有動力了~

一言以蔽之就是:這是一個前端JS框架。OK~這句話似乎又衍生了更多問題,用在什麼地方?好不好用?優點是什麼?跟Angular比較起來差別是?為什麼我要用?

OK,為什麼我要用?大部分情況下,我們都會有專案期限的壓力,通常在這種壓力下,一般人(小編就是一般人啦)就會選用自己最擅長,最有把握的語言(工具)去完成專案,這有什麼優缺點呢優點就是專案如期完成的機率大幅提升。缺點呢井底之蛙,你會的永遠就是這些,你的速度永遠這麼慢,且你永遠不知道世界有多大,進步有多快。保哥曾在篇網誌上說過,一個工程師的價值在於它是否願意花時間去縮短他的coding時間。簡單說就是提升工作效率啦。一個網站從到尾自己刻的時代已經過去了,什麼都就講究自動化的今天,資料要越乾淨越好,工程師做的事情要越少越好,所以這些前端框架就應運而生。

比起Angular又如何呢?先說小弟我其實是支持Angular啦,特別是A2已經出到RC版的今天,差不多可以進場了,後面再講資料流的時候我會跟大家提到,不過有時專案需要,我們還是要搞懂React這傢伙在搞什麼。


接下來開始進入React的核心
React並不能算是個完整個框架,官方推出React的時候希望落實關注點分離,讓大家可以專注於View的開發,所以充其量React只是MVC中的View而已(有一點點的M),其最重要也是最強大的功能就是Render:宣染 VDOM(虛擬DOM),我敢保證,你看完說明肯定還是不懂我在說什麼,沒關係就直接進我們第一個範例吧

第一支React
1
2
3
4
5
6
7
8
9
10
11
HTML:
<div id="example"></div>

JSX:
ReactDOM.render(
    <h1>Hello, world!</h1>, //將這個物件,丟到下面這個元件之中
    document.getElementById('example') //丟到我裡面吧
);

render result:
<div id="example"><h1>Hello, world!</h1></div>
把這些寫在JS代碼中的HTML(VDOM)宣染到畫面上,這就是React最大的賣點,集中撰寫,跟宣染速度快,因為它會透過底層演算,用最小的幅度去修改畫面(不過最近被VueAngular2打臉了),附上一篇文章


React來說,很重要的一個觀念,你畫面上看到的所有東西都是元件(components)

Component props
1
2
3
4
5
6
7
8
9
10
11
12
13
var HelloMessage = React.createClass({
    render: function() {
      return <h1>Hello {this.props.name}</h1>; => 所以這邊會輸出Hello John
    }
});

ReactDOM.render(
    <HelloMessage name="John" />, =>設定這物件的name屬性為John
    document.getElementById('example')
);

開嗆時間:
angular 這樣寫就好了 <h1 ng-init="name='John'">Hello {{name}}</h1>


所以其中一個React很大的優勢就是,可以局部重購,因為可以把畫面拆成許多Components,元件中也可以包元件
JSX{}內可以正常的寫JS,若是放入array,也會批次被render出來

Array for React
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var names = ['Alice', 'Emily', 'Kate'];
ReactDOM.render(
      <div>
      {
          names.map(function (name) {
              return <div>Hello, {name}!</div>
          })
      }
      </div>,
      document.getElementById('example')
);
Demo

開嗆時間:
angular 這樣寫就好了 <div ng-repeat="name in names">{{name}}</div>



Children
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 var Comment = React.createClass({
    render: function() {
        return ( < div className = "comment" >
            < h2 className = "commentAuthor" >
                { this.props.author}
            < /h2>
            {this.props.children} < /div>
        );
    }
});
var CommentList = React.createClass({
    render: function() {
        return (
            < div className = "commentList" >
                < Comment author = "Pete Hunt" > This is one comment < /Comment>
                < Comment author = "Jordan Walke" > This is * another * comment < /Comment >
             < /div>
        );
       }
});
ReactDOM.render(
    <CommentList />,
    document.getElementById('container')
);

/*
這裡可以用children取子節點,如果子節點是很多個物件,那取回來就會是陣列,例如這樣
</Parent>
    <ChildrenA />
    <ChildrenB />
    <ChildrenC />
</Parent>
*/



attr 共用屬性
1
2
3
4
5
6
7
8
9
10
11
var attr = {};  => 共用的屬性,不想重複打可以這樣做
attr['id'] = "a";
attr['name'] = "b";
attr['class'] = "c";

render( 
    <input {...attr} onChange={this.valueChg.bind(this,tr_index,td_index)}/>
) //event如果要綁定參數,必須用bind(this......)的方式呼叫

render result:
<input id="a" name="b" class="c" onChange={this.valueChg.bind(this,tr_index,td_index)}/>



event, refs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myTextInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput"/> // reference參考,在這可以把它當成向指定idㄧ樣指定物件
        <input type="button" value="Focus the text input" onClick={this.handleClick} /> //onClick觸發handleClick()
      </div>
    );
  }
});

ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);
Demo
*
若是有兩個物件ref 到相同名稱,呼叫時會呼叫到後面的物件。


state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 var LikeButton = React.createClass({
  getInitialState: function() { =>初始化state
    return {liked: false}; => liked = false;
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked}); => 設定liked 反向
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);



React裡面有個很重要的觀念,state改變,元件就會重新render,所以要想改變畫面上的東西,只有model改變是不夠的,看看以下範例

rerender
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 var Input = React.createClass({
    getInitialState: function() { // 設定初始state
      return {value: 'Hello!'};
    },
    handleChange: function(event) {
        this.setState({value: event.target.value}); // 若不藉此改變state畫面上的數值是不會更動的,除非是改用defaultValue,但如此便無法掌握資料了
    },
    render: function () {
        var value = this.state.value;
        return (
          <div>
            <input type="text" value={value} onChange={this.handleChange} />
            <p>{value}</p>
          </div>
        );
    }
});

ReactDOM.render(<Input/>, document.getElementById('example'));

開嗆時間:
Angular只要這樣寫
<input type="text" ng-model={value} />
<p>{{value}}</p>
this.props通常是使用在父元素傳值給子元素的情境上this.props 以及 this.state。這兩個屬性有不同的特性與行為,在使用上,官方建議靜態資料使用this.props,動態資料使用this.state


value defaultValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var MyComponent = React.createClass({
    getInitialState: function() {
        return {a: "a"};
    },
    handleClick1: function() {
        this.refs.myTextInput1.value="b";
        this.refs.myTextInput2.value="b";
    },
    handleClick2: function() {
        this.setState({a:"b"});
    },
    change1: function(e) {
        alert(e.target.value)
    },
    change2: function(e) {
        alert(e.target.value)
    },
    render: function() {
        return (
            <div>
                <input type="text" ref="myTextInput1" defaultValue={this.state.a} onChange={this.change1}/> 
//直沒有被綁定,可以透過onChange偵聽,也可以透過事件改變,但資料不好掌握
                <br/> // 因為必須是完整的巢狀式迴圈,所以一定要加上反斜線
                <input type="text" ref="myTextInput2" value={this.state.a} onChange={this.change2}/> 
//值被state.a綁定了,也可以透過onChange偵聽,但值不會更改,但可以被事件改變(不觸發onChange),但這樣它的優勢(資料好掌握)就沒了,一旦資料被rerender,值會再回到state.a
                <br/>
                <input type="button" value="changeA" onClick={this.handleClick1} />
                <br/>
                <input type="button" value="changeState" onClick={this.handleClick2} />
            </div>
        );
    }
});
ReactDOM.render(
    <MyComponent />,
    document.getElementById('container')
);
 Demo
關於value & defaultValue 這有編文章寫得好棒棒 傳送門



className
1
2
3
4
5
6
7
8
9
10
11
 var HelloMessage = React.createClass({
    render: function() {
      var classString = "msg";
      return <div className={classString}>Hello, I'll be there</div>; => 其實就是正常htmlclass,但class這名詞是React的專有名詞,所以不能直接使用
    }
});

ReactDOM.render(
    <HelloMessage />,
    document.getElementById('example')
);


其實React也可以做到two-way-data-binding

two-way-data-binding
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 var WithLink = React.createClass({
    /* 載入 React.addons.LinkedStateMixin 中的方法 (this.linkState)*/
    mixins: [React.addons.LinkedStateMixin], //應該是類似擴充方法
    getInitialState: function () {
        return {message: 'Hello!'}
    },
    render: function () {
        /*邏輯處理的地方*/
            return (
                <div>
                <input type='text' valueLink={this.linkState('message')} />
                {this.state.message}
                </div>

        )
    }
});
但是two-way-data-binding做完可以在render裡面做些簡單的視覺邏輯,但render裡面本來就不適合放資料處理邏輯,所以如果真的要走資料流,也不適合這種方式,這也是這個好用的功能卻一值沒沒無聞的原因。


大致上,元件執行生命週期方法的情形可分為四種
初始化,第一次 render
getDefaultProps()
getInitialState()
componentWillMount()
render()
componentDidMount()
props 發生改變時
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps, nextState)
componentWillUpdate(nextProps, nextState)
render() => 我試不出來
componentDidUpdate(prevProps, prevState)
state 發生改變時
shouldComponentUpdate(nextProps, nextState)
componentWillUpdate(nextProps, nextState)
render()
componentDidUpdate(prevProps, prevState)
元件 unmount 卸載時
componentWillUnmount()

來看個簡單範例

生命週期範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 var Hello = React.createClass({
    getInitialState: function () {
        return {
            opacity: 1.0
        };
    },

    componentDidMount: function () { => 必須在宣染完後才能開始動作
        this.timer = setInterval(function () {
            var opacity = this.state.opacity;
            opacity -= .05;
            if (opacity < 0.1) {
                opacity = 1.0;
            }
            this.setState({
                opacity: opacity => 變更透明度後重新寫入statereact會自動重新宣染該物件
            });
        }.bind(this), 100); => bind(this)JSX裡面會常常用到,但這不是JSX特有的寫法,而是JS本身的特性,告訴在這個func裡面的this是誰
    },

    render: function () {
        return (
            <div style={{opacity: this.state.opacity}}>
                Hello {this.props.name}
            </div>
        );
    }
});

ReactDOM.render(
    <Hello name="world"/>,
    document.getElementById('example')
);


Ajax
react本身是無法做到這件事的,必須透過其他框架,例如jQueryReact還有個很大的特色是,跟各家框架都幾乎完美相容,低耦合,甚至它已可以做到類似lazyLoad的效果

var RepoList = React.createClass({
  getInitialState: function() {
    return {
      loading: true,
      error: null,
      data: null
    };
  },

  componentDidMount() {
    this.props.promise.then( => 等待ajax取資料回來
      value => this.setState({loading: false, data: value}),
      error => this.setState({loading: false, error: error}));
  },

  render: function() {
    if (this.state.loading) {// 開始資料分析
      return <span>Loading...</span>;//資料Loading
    }
    else if (this.state.error !== null) {
      return <span>Error: {this.state.error.message}</span>;
    }
    else {
      var repos = this.state.data.items;
      var repoList = repos.map(function (repo, index) { // map在寫react中也常用到,作用類似forEach把陣列資料處裡回傳(angular 就是 ng-repeat)
        return (
          <li key={index}><a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}</li>
        );
      });
      return (
        <main>
          <h1>Most Popular JavaScript Projects in Github</h1>
          <ol>{repoList}</ol>
        </main>
      );
    }
  }
});

ReactDOM.render(
    <RepoList promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')} />,//$.getJSONjQuery的方法
    document.getElementById('example')
);
所以網路上許多人也會用React+backbone讓它變成一個完整的MVC框架,讓它開發起來更加靈活


甚至,React還可以寫在server端,當然前提是你要用同樣是JS開發的NodeJS伺服器。這樣可以在server端直接render出畫面,並且在前端繼續開始react的生命週期。
不過感覺有點像phpecho 輸出html .netlazor輸出html一樣,前後端就又絞在一起了,成為所謂的全端程式,小編是不太喜歡這樣啦
.....下面就nodejsreact混用,沒啥好介紹的。

React in server
 var http = require('http'),
    browserify = require('browserify'),
    literalify = require('literalify'),
    React = require('react'),
    ReactDOMServer = require('react-dom/server');

var App = require('./app');

http.createServer(function(req, res) {
  if (req.url == '/') {
    res.setHeader('Content-Type', 'text/html');
    var props = {
      items: [
        'Item 0',
        'Item 1'
      ]
    };
    var html = ReactDOMServer.renderToStaticMarkup(
      <body>
        <div id="content" dangerouslySetInnerHTML={{__html:
          ReactDOMServer.renderToString(<App items={props.items}/>)
        }} />

        <script dangerouslySetInnerHTML={{__html:
        'var APP_PROPS = ' + JSON.stringify(props) + ';'
        }}/>
        <script src="//fb.me/react-0.14.0.min.js"/>
        <script src="//fb.me/react-dom-0.14.0.min.js"/>
        <script src="/bundle.js"/>
      </body>
    );
    res.end(html);

  } else if (req.url == '/bundle.js') {
    res.setHeader('Content-Type', 'text/javascript');
    browserify()
      .add('./browser.js')
      .transform(literalify.configure({
        'react': 'window.React',
        'react-dom': 'window.ReactDOM',
      }))
      .bundle()
      .pipe(res);
  } else {
    res.statusCode = 404;
    res.end();
  }
}).listen(3000, function(err) {
    if (err) throw err;
    console.log('Listening on 3000...');
})

學到這裡已經可以開始開發小型的React專案了,還可以有簡單的資料流。

=================================================  我是分隔線,休息一下,喝口水 =========================================================


WTF,簡單資料流??
你應該有發現他的資料流似乎都是父傳子,我想要兄弟相傳,親戚相傳,薪火相傳可以嗎?原則上是可以的,可以透過共同的祖先來資料源與資料處裡。
那問題來了,如果沒有共同的祖先呢?記得React的特性就是東一塊西一塊嗎?而且React當初設計本來就不是為了傳遞資料而設計,所以在資料傳遞上的寫法都會很複雜,所以如果有頻繁的資料傳遞,開發簡單的小程式你就頭大了(說好的React學習曲線平緩呢?),所以這時候官方就提出了一個設計模式Flux,看清楚我講的不是框架,不是語言,是設計模式!!
那這個Flux又是何方神聖呢?這個.......我們就留到後話再說吧!