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
相关文章
相关标签/搜索