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