react入門

生命週期函數:(每一個組件都存在生命週期函數)

理論:

  1. init初始化階段:constructor(state props)
  2. mounting(掛載):componentWillMount(在組件即將被掛載到頁面的時刻自動執行) =>render=>componentDidMount(組件被掛載到頁面以後,自動被執行)

    dom更新時,只有render會被執行[在index裏使用React.StrictMode,會被render兩次]html

  3. updation:(1)shouldComponentUpdate(組件被更新以前,會被自動執行==>返回一個布爾值[false:不更新,true:更新]);(2)componentWillUpdate組件被更新以前,它會被自動執行(在shouldComponentUpdate以後執行==>若shouldComponentUpdate返回true則執行,不然不執行)(3)render ==> 從新渲染(4)componentDidUpdate組件更新完成以後,會被執行(5)componentWillReceiveProps:(有props的時候纔會執行 )當一個組件從父組件接收參數,只要父組件的render函數被【從新】執行了,子組件的這個生命週期函數就會被執行【注意:* (1)若是這個組件第一次存在於父組件中,不會執行

    * (2)若是這個組件以前已經存在於父組件中,纔會執行】react

  4. unmounting:componentWillUnmount當這個組件即將被從頁面中剔除的時候,會被執行

實際應用:

  • shouldComponentUpdate:判斷組件是否被真正更新 提高子組件性能
  • componentDidMount:ajax數據的獲取

Redux

三大原則

  1. store必須是惟一的
  2. 只有store能改變本身的內容(並非在reducer更新的==>是拿到reducer的數據更新本身)
  3. reducer必須是純函數(純函數:給固定的輸入。就必定有固定的輸出,並且不會有任何的反作用 若存在date或ajax則不是固定輸出。不含反作用:不會對傳入參數進行修改)

核心api

  1. createStore:是生成一個 store 對象
  2. store.dispatch:能夠觸發傳入的action
  3. store.getState:使得state數據與store裏的數據同步
  4. store.subscribe:在Store更新時執行一些操做(組件去訂閱store ,只要store發生改變就會自動執行該函數)

使用chrome的redux插件

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

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

actionCreate:

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)   
 })  
}}

使用中間件redux-saga

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

react-redux

index.js:api

import {Provider} from 'react-redux'
<Provider store={store}>        
    <App/>        
</Provider>

存在的反作用

  1. 綁定事件
  2. 網絡請求
  3. 訪問dom

反作用的時機

  1. Mount以後 componentDidMount
  2. 2.Update以後 componentDidUpdate
  3. 3.Unmount以前 componentWillUnmount

Hook

hooks的優點

  1. 方便複用狀態邏輯 custom hooks
  2. 反作用的關注點分離
  3. 函數組件無this問題

useEffect

render以後調用(componentDidMount)、(componentDidUpdate)返回clean callback(清除上一次的反作用遺留的狀態)至關於==>componentWillUnmount

實現一個hook

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>  );
}

errorBoundary 錯誤邊界

捕獲組件報錯(componentDidCatch):

constructor(props) {    
    super(props);    
    this.state = {      
    hasError: false,    
    };  
}
componentDidCatch() {
    console.log('componentDidCatch');     
    this.setState(() => {      
        return { 
            hasError: true
         };   
     });  
}

lazy & Suspense

lazy 是 react 提供的組件懶加載的能力:React.lazy接受一個函數,這個函數內部調用import()動態導入。它必須返回一個Promise,該Promise須要resolve一個defalut exportReact組件。

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>      
      );    
    }  
}}

Context

Context提供了一種方式,可以讓數據在組件🌲中傳遞而沒必要一級一級手動傳遞

provider & consumer:

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>    
    ); 
 }}

createContext:

建立一個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>    
        ); 
     }
}

memo:控制什麼時候從新渲染組件

組件僅在它的 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提高性能

淺比較(shallowEqual)

即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,初始化依舊是輸出constructorrender,可是當點擊按鈕時,界面上沒有變化,也沒有輸出render,證實沒有渲染。

能夠從下面的註釋中看到,每點擊一次按鈕,咱們想要修改的arr的值已經改變,而這個值將去修改this.state.arr,但由於在PureComponent淺比較這個數組的引用沒有變化,因此沒有渲染,this.state.arr也沒有更新。在this.setState()之後,值是在render的時候更新的。

當這個組件是繼承自Component的時候,初始化依舊是輸出constructorrender。當點擊按鈕時,界面上出現了變化,即咱們打印處理的arr的值輸出,並且每點擊一次按鈕都會輸出一次render,證實已經從新渲染,this.state.arr的值已經更新,因此咱們能在界面上看到這個變化。

✨用擴展運算符產生新數組,使this.state.arr的引用發生了變化。初始化的時候輸出constructorrender後,每次點擊按鈕都會輸出render,界面也會變化。無論該組件是繼承自Component仍是PureComponent。

changeState = () => {
    let { arr } = this.state;
    this.setState({
      arr: [...arr, '2']
    })

PureComponent:

不只會影響自己,同時也會影響子組件==>

PureComponent最佳狀況是展現組件

useEffect

✨默認狀況下,它在第一次渲染以後和每次更新以後都會執行

沒有使用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:

useCallback本質上是添加了一層依賴檢查。它解決了每次渲染都不一樣的問題,咱們可使函數自己只在須要的時候才改變。

const onClick = useCallback(() => {

console.log('click');    
// setClickCount((clickCount)=>clickCount + 1)    
console.log(couterRef.current);    
couterRef.current.speck(); //利用ref去獲取組件的值

}, [couterRef]); //等價於usecallback

在useEffect裏請求數據⚠️

引用: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>)
}

useReducer:

參考: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(可選)組成:

  • type: 本次操做的類型,也是 reducer 條件判斷的依據。(用type來表示具體的行爲類型(登陸、登出、添加用戶、刪除用戶等)
  • payload: 提供操做附帶的數據信息(如增長書籍,能夠攜帶具體的book信息)
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
  • 但願構建自動化測試用例來保證程序的穩定性
  • 須要在深層子組件裏面去修改一些狀態
  • 應用程序比較大,但願UI和業務可以分開維護

useContext

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} />;
  }
}

useContext版login

引用: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相比回調函數的優點:

  • 對比回調函數的自定義命名,Context的Api更加明確,咱們能夠更清晰的知道哪些組件使用了dispatch、應用中的數據流動和變化。這也是React一直以來單向數據流的優點。
  • 更好的性能:若是使用回調函數做爲參數傳遞的話,由於每次render函數都會變化,也會致使子組件rerender。【固然咱們可使用useCallback解決這個問題,但相比useCallbackReact官方更推薦使用useReducer,由於React會保證dispatch始終是不變的,不會引發consumer組件的rerender。】

總結:

  • 頁面state很簡單:能夠直接使用useState
  • 頁面state比較複雜(state是一個對象或者state很是多散落在各處):userReducer
  • 頁面組件層級比較深,而且須要子組件觸發state的變化:useReducer + useContext
相關文章
相關標籤/搜索