dom更新時,只有render會被執行[在index裏使用React.StrictMode,會被render兩次]html
* (2)若是這個組件以前已經存在於父組件中,纔會執行】react
import { createStore, compose,applyMiddleware } from 'redux'; import saga from './saga' const sagaMiddleware = createSagaMiddleware() const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const enhancer = composeEnhancers( // applyMiddleware(thunk) applyMiddleware(sagaMiddleware) // other store enhancers if any ); sagaMiddleware.run(saga)
建立store:webpack
const store = createStore( reducer, enhancer //redux中間件);
reducer 能夠接收state,可是不能修改stateios
import { DELETE_TODO_IT } from './actionTypes';web
const defaultState = { inputValue: '', list: [],};ajax
export default (state = defaultState, action) => {
if (action.type === CHANGE_INPUT_VALUE) {
//深拷貝 const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState; }
}chrome
state:store裏面的數據(上一次存儲的數據)redux
action:dispatch過來的actionaxios
export const initListAction = (data) =>({ type:INIT_LIST_VALUE, data})
// 使用了thunk以後 纔可使用函數式返回 export const getToDoList = ()=>{ return (dispatch)=>{ axios.get('https://.../api/todolist').then(res=> { const data = res.data const action = initListAction(data) dispatch(action) }) }}
import { takeEvery, put } from 'redux-saga/effects' import { GET_INIT_LIST } from './actionTypes' import { initListAction } from './actionCreators' import axios from 'axios' function* getInitList() { try { const res = yield axios.get('https://.../api/todolist') const data = res.data const action = initListAction(data) yield put(action) //等待處理完成 } catch (error) { console.log(error); } } // takeEvery:捕抓每個action的類型 function* mySage() { yield takeEvery(GET_INIT_LIST, getInitList) } export default mySage
index.js:api
import {Provider} from 'react-redux'
<Provider store={store}> <App/> </Provider>
render以後調用(componentDidMount)、(componentDidUpdate)返回clean callback(清除上一次的反作用遺留的狀態)至關於==>componentWillUnmount
function useSize() { const [size, setSize] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); useEffect(() => { window.addEventListener('resize', onResize, false); return () => { window.removeEventListener('resize', onResize, false); }; }, []); const onResize = useCallback(() => { setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); }, []); return size } function useCount(defaultCount) { const [count, setCount] = useState(() => { return defaultCount || 0; //延遲初始化 只會執行一次 }); const it = useRef(); useEffect(() => { it.current = setInterval(() => { setCount((count) => count + 1); }, 1000); }, []); useEffect(() => { if (count >= 10) clearInterval(it.current); }); return [count, setCount]; }
function useCounter(count) { const size = useSize() return <h1>{count},{size.width}x{size.height}</h1>; //hook能夠返回jsx }
function App(props) { const [count, setCount] = useCount(0); const Counter = useCounter(count); const size = useSize() return ( <div> <button onClick={() => { setCount(count + 1);}}> add </button> <p>click:({count})</p> {Counter},{size.width}x{size.height} </div> ); }
constructor(props) { super(props); this.state = { hasError: false, }; } componentDidCatch() { console.log('componentDidCatch'); this.setState(() => { return { hasError: true }; }); }
lazy 是 react 提供的組件懶加載的能力:React.lazy
接受一個函數,這個函數內部調用import()
動態導入。它必須返回一個Promise
,該Promise
須要resolve
一個defalut export
的React
組件。
const About = lazy(() => import(/* webpackChunkName: "about" */ './About.jsx'));
實現一個lazy加載的component:
render() { if (this.state.hasError) { return <div>error</div>; } else { return ( <div> <Suspense fallback={<div>loading</div>}> <About></About> </Suspense> </div> ); } }}
render() { const { battery, online } = this.state; return ( <BatteryContext.Provider value={battery}> <OnlineContext.Provider value={online}> <button onClick={() => this.setState({ battery: battery - 1 })}>add</button> <button onClick={() => this.setState({ online: !online })}> switch</button> <Middle /> </OnlineContext.Provider> </BatteryContext.Provider> ); }} class Middle extends Component { render() { return <Leaf />; }}
看起來沒有那麼優美的consumer
class Leaf extends Component {render() { return ( <BatteryContext.Consumer> {(battery) => ( <OnlineContext.Consumer>{(online) => ( <h1> battery:{battery},Online:{String(online)} </h1> )} </OnlineContext.Consumer> )} </BatteryContext.Consumer> ); }}
建立一個context對象: 組件會向組件所處的樹中距離最近的那個Provider進行匹配context。當組件所處的樹沒有匹配到Provider (不使用Provider組件) 時,defaultValue參數纔會生效。
cont TextContext = React.createContext(defaultValue);
看起來比較優雅的comsumer
const BatteryContext = createContext(); const OnlineContext = createContext(); class Leaf extends Component { static contextType = BatteryContext; render() { const battery = this.context return ( <h1>battery:{battery}</h1> ); } }
組件僅在它的 props 發生改變的時候進行從新渲染。一般來講,在組件樹中 React 組件,只要有變化就會走一遍渲染流程。可是經過 PureComponent 和 React.memo(),咱們能夠僅僅讓某些組件進行渲染。
const Foo2 = memo(function Foo2(props) { console.log('foo2 render'); return <div>{props.person.age}</div> //防止無心義的從新渲染 } )
const Foo2 = React.memo(props => { return <div>Foo2</div>; });
因爲 React.memo() 是一個高階組件,你可使用它來包裹一個已有的 functional component
const Foo1 = props => <div>this is foo1</div>; const Foo2 = React.memo(Foo1);
引用:https://www.jianshu.com/p/c41bbbc20e65
PureComponent經過prop和state的淺比較來實現shouldComponentUpdate,某些狀況下能夠用PureComponent提高性能
即react源碼中的一個函數,而後根據下面的方法進行是否是PureComponent
的判斷,幫咱們作了原本應該咱們在shouldComponentUpdate
中作的事情
if (this._compositeType === CompositeTypes.PureClass) { shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState); }
Component的處理方式
shouldComponentUpdate(nextProps, nextState) { return (nextState.person !== this.state.person); }
來講一個🌰:
class IndexPage extends PureComponent{ this.state = { arr:['1'] }; changeState = () => { let { arr } = this.state; arr.push('2'); console.log(arr); // ["1", "2"] // ["1", "2", "2"] // ["1", "2", "2", "2"] // .... this.setState({ arr }) }; render() { console.log('render'); return ( <div> <button onClick={this.changeState}>點擊</button> </div> <div> {this.state.arr.map((item) => { return item; }) } </div> ); }}
這個組件是繼承自PureComponent
,初始化依舊是輸出constructor
和render
,可是當點擊按鈕時,界面上沒有變化,也沒有輸出render
,證實沒有渲染。
能夠從下面的註釋中看到,每點擊一次按鈕,咱們想要修改的arr
的值已經改變,而這個值將去修改this.state.arr
,但由於在PureComponent
中淺比較
這個數組的引用沒有變化,因此沒有渲染,this.state.arr
也沒有更新。在this.setState()
之後,值是在render
的時候更新的。
當這個組件是繼承自Component
的時候,初始化依舊是輸出constructor
和render。
當點擊按鈕時,界面上出現了變化,即咱們打印處理的arr
的值輸出,並且每點擊一次按鈕都會輸出一次render
,證實已經從新渲染,this.state.arr
的值已經更新,因此咱們能在界面上看到這個變化。
✨用擴展運算符
產生新數組,使this.state.arr
的引用發生了變化。初始化的時候輸出constructor
和render
後,每次點擊按鈕都會輸出render
,界面也會變化。無論該組件是繼承自Component
仍是PureComponent。
changeState = () => { let { arr } = this.state; this.setState({ arr: [...arr, '2'] })
PureComponent:
不只會影響自己,同時也會影響子組件==>
PureComponent最佳狀況是展現組件
✨默認狀況下,它在第一次渲染以後和每次更新以後都會執行
沒有使用useEffect的class:
class App extends Component { state = { count: 0, size:{ width:document.documentElement.clientWidth, height:document.documentElement.clientHeight } };
最好使用類屬性的方法聲明 能夠確保this的指向!! 或使用:@bind() ==>最好的方案 onResize = () => { this.setState({ size:{ width:document.documentElement.clientWidth, height:document.documentElement.clientHeight } }) };
componentDidMount() { document.title = this.state.count; window.addEventListener('resize', this.onResize, false); } componentWillUnmount() { window.removeEventListener('resize', this.onResize, false); } componentDidUpdate() { document.title = this.state.count; } render() { const { count,size } = this.state; return ( <div> <button onClick={() => { this.setState({ count: count + 1 }); }}> add </button> <p>click:({count})</p> <h5>{size.width} {size.height}</h5> </div> ); } }
使用useState & useEffect:
const [count, setCount] = useState(() => { console.log('init count'); return props.defaultCount || 0; //延遲初始化 只會執行一次 });
const [size, setSize] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); const onResize = ()=>{ setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }) }
useEffect:分開處理不一樣的事件 互不干擾
useEffect(() => { console.log('count',count); },[count]);//size的改變並不會觸發該useEffect
視圖銷燬以前執行 有兩種狀況:1. 從新渲染 2. 組件卸載
useEffect(() => { window.addEventListener('resize', onResize, false); return ()=>{ window.removeEventListener('resize', onResize, false); } },[]);//避免重複綁定與解綁 只會執行一次
🐶other:
被async包裹的函數會返回一個promise對象,可是effect hook應當return nothing or a clean up function,所以會收到警告⚠️。因此async只能間接使用:
useEffect(() => { const getData = async () => { const result = await axios( 'https://...', ); setData(result.data); }; getData(); }, []);
useCallback本質上是添加了一層依賴檢查。它解決了每次渲染都不一樣的問題,咱們可使函數自己只在須要的時候才改變。
const onClick = useCallback(() => {
console.log('click'); // setClickCount((clickCount)=>clickCount + 1) console.log(couterRef.current); couterRef.current.speck(); //利用ref去獲取組件的值
}, [couterRef]); //等價於usecallback
引用:https://www.jianshu.com/p/7813d0c2ae67
const useDataApi = (initialUrl, initialData) => { const [data, setData] = useState(initialData); const [url, setUrl] = useState(initialUrl); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); useEffect(() => { const fetchData = async () => { setIsError(false); setIsLoading(true); try { const result = await axios(url); setData(result.data); } catch (error) { setIsError(true); } setIsLoading(false); }; fetchData(); }, [url]); return [{ data, isLoading, isError }, setUrl]; }; function App() { const [query, setQuery] = useState('redux'); const [{ data, isLoading, isError }, doFetch] = useDataApi( 'https://hn.algolia.com/api/v1/search?query=redux', { hits: [] }, ); return (<div>...</div>) }
參考:https://www.jianshu.com/p/14e429e29798
useReducer 接受一個 reducer 函數做爲參數,reducer 接受兩個參數一個是 state另外一個是 action。而後返回一個狀態 count 和 dispath,count 是返回狀態中的值,而 dispatch 是一個能夠發佈事件來更新 state 的。
import React,{useReducer} from 'react' export default function ReducerDemo() { const [count, dispath] = useReducer((state,action)=> { //... }, 0); return ( <div> <h1 className="title">{count}</h1> </div> ) }
👀一個🌰:
import React,{useReducer} from 'react' export default function ReducerDemo() { const [count, dispath] = useReducer((state,action)=> { switch(action){ case 'add': return state + 1; case 'sub': return state - 1; default: return state; } }, 0); return ( <div> <h1 className="title">{count}</h1> <button className="btn is-primary" onClick={()=> dispath('add')} >Increment</button> <button className="btn is-warnning" onClick={()=> dispath('sub')} >Decrement</button> </div> ) }
關於state:不能再原有的state上進行修改,須要從新copy一個。(Immutable:每次都返回一個newState)
✨對action的理解:
用來表示觸發的行爲,一個常規的Action對象一般有type和payload(可選)組成:
const action = { type: 'addBook', payload: { book: { bookId, bookName, author, } } } function bookReducer(state, action) { switch(action.type) { // 添加一本書 case 'addBook': const { book } = action.payload; return { ...state, books: { ...state.books, [book.bookId]: book, } }; case 'sub': // .... default: return state; } }
實現一個useReducer版的login:
參考:https://www.jianshu.com/p/566f0d79ca7b
const initState = { name: '', pwd: '', isLoading: false, error: '', isLoggedIn: false, } function loginReducer(state, action) { switch(action.type) { case 'login': return { ...state, isLoading: true, error: '', } case 'success': return { ...state, isLoggedIn: true, isLoading: false, } case 'error': return { ...state, error: action.payload.error, name: '', pwd: '', isLoading: false, } default: return state; } } function LoginPage() { const [state, dispatch] = useReducer(loginReducer, initState); const { name, pwd, isLoading, error, isLoggedIn } = state; const login = (event) => { event.preventDefault(); dispatch({ type: 'login' }); login({ name, pwd }) .then(() => { dispatch({ type: 'success' }); }) .catch((error) => { dispatch({ type: 'error' payload: { error: error.message } }); }); } return ( // 返回頁面JSX Element ) }
使用reducer的場景:
state
是一個數組或者對象state
變化很複雜,常常一個操做須要修改不少state
Context
的做用就是對它所包含的組件樹提供全局共享數據的一種技術
1.建立須要共享的context
const ThemeContext = React.createContext('light');
2.使用 Provider 提供 ThemeContext 的值,Provider所包含的子樹均可以直接訪問ThemeContext的值
class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } }
3.Toolbar 組件並不須要透傳 ThemeContext
function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); }
4.使用共享 Context
function ThemedButton(props) { const theme = useContext(ThemeContext); render() { return <Button theme={theme} />; } }
引用:https://www.jianshu.com/p/eddb25cda5f0
// 定義初始化值 const initState = { name: '', pwd: '', isLoading: false, error: '', isLoggedIn: false, } // 定義state[業務]處理邏輯 reducer函數 function loginReducer(state, action) { switch(action.type) { case 'login': return { ...state, isLoading: true, error: '', } case 'success': return { ...state, isLoggedIn: true, isLoading: false, } case 'error': return { ...state, error: action.payload.error, name: '', pwd: '', isLoading: false, } default: return state; } } // 定義 context函數 const LoginContext = React.createContext(); function LoginPage() { const [state, dispatch] = useReducer(loginReducer, initState); const { name, pwd, isLoading, error, isLoggedIn } = state; const login = (event) => { event.preventDefault(); dispatch({ type: 'login' }); login({ name, pwd }) .then(() => { dispatch({ type: 'success' }); }) .catch((error) => { dispatch({ type: 'error' payload: { error: error.message } }); }); } // 利用 context 共享dispatch return ( <LoginContext.Provider value={{dispatch}}> <...> <LoginButton /> </LoginContext.Provider> ) } function LoginButton() { // 子組件中直接經過context拿到dispatch,觸發reducer操做state const dispatch = useContext(LoginContext); const click = () => { if (error) { // 子組件能夠直接 dispatch action dispatch({ type: 'error' payload: { error: error.message } }); } } }
能夠看到在useReducer結合useContext,經過context把dispatch函數提供給組件樹中的全部組件使用,而不用經過props添加回調函數的方式一層層傳遞。
使用Context相比回調函數的優點:
useCallback
React官方更推薦使用useReducer,由於React會保證dispatch始終是不變的,不會引發consumer組件的rerender。】state
很簡單:能夠直接使用useState
state
比較複雜(state是一個對象或者state很是多散落在各處):userReducerstate
的變化:useReducer + useContext