Conext
- context可以实现某些值在context上下文之间传递
import React, { Component, createContext } from 'react'; import './App.css'; class Bottom extends Component { render() { return ( <MyContext.Consumer> { (value) => <h1>{value.initValue}</h1> } </MyContext.Consumer> ) } } class Middle extends Component { render() { return <Bottom/> } } const MyContext = createContext(); function App() { return ( <MyContext.Provider value={{initValue: 1}}> <Middle/> </MyContext.Provider> ); } export default App;
- 如果存在多个context也是可以的
import React, { Component, createContext } from 'react'; import './App.css'; class Bottom extends Component { render() { return ( <MyContext.Consumer> { (value) => ( <NextContext.Consumer> { (nextValue)=> <h1>value: {value.initValue}, nextValue: {nextValue.nextInitValue}</h1> } </NextContext.Consumer> ) } </MyContext.Consumer> ) } } class Middle extends Component { render() { return <Bottom/> } } const MyContext = createContext(); const NextContext = createContext(); function App() { return ( <MyContext.Provider value={{initValue: 1}}> <NextContext.Provider value={{nextInitValue: 2}}> <Middle/> </NextContext.Provider> </MyContext.Provider> ); } export default App;
contextType
- 之前我们必须使用Customer才能共享Provider中的变量,这样使得我们的代码变得不优雅,contextType就是解决这个问题的
- contextType只能用到类组件并且只能有一个context
import React, { Component, createContext } from 'react'; import './App.css'; const MyContext = createContext(); class Bottom extends Component { // 声明 static contextType = MyContext; render() { const value = this.context; return ( <h1>{value.initValue}</h1> ) } } class Middle extends Component { render() { return <Bottom/> } } function App() { return ( <MyContext.Provider value={{initValue: 1}}> <Middle/> </MyContext.Provider> ); } export default App;
lazy,Suspense和ErrorBoundary
-
lazy加载的代码webpack会做拆分单独打包为一个文件,Suspense的fallback需要指定在lazy文件未加载之前显示的组件
import React, { Component, lazy, Suspense } from 'react'; import './App.css'; const LazyCom = lazy(()=>import('./lazyCom.jsx')) class App extends Component { state = { error: true } componentDidCatch() { this.setState({ error: true }) } render() { const { error } = this.state; if(error) { return <div>error</div> } return <div> <Suspense fallback={<div>loading</div>}> <LazyCom/> </Suspense> </div> } } export default App;
-
如果我们不写Suspense但是使用了lazy会抛出异常,界面什么也不显示,所以我们需要做错误边界
import React, { Component, lazy, Suspense } from 'react'; import './App.css'; const LazyCom = lazy(()=>import('./lazyCom.jsx')) class App extends Component { state = { error: true } // 类似于componentDidCatch,用来将异常传递给state // static getDerivedStateFromError() { // return { // hasError: true // } // } componentDidCatch() { this.setState({ error: true }) } render() { const { error } = this.state; if(error) { return <div>error</div> } return <div> <LazyCom/> </div> } } export default App;
memo
-
传统的react组件中,父组件渲染一定会渲染自组件,所以我们需要重写父组件的shouldComponentUpdate或者使用PureComponent
class Test extends PureComponent { render() { console.info("render"); return null; } } class App extends Component { state = { count: 0, } onClick = () => { const { count } = this.state; this.setState({ count: count + 1 }); } render() { console.info("red") return <div> <button onClick={this.onClick}>加1</button> <Test name="text"/> </div> } } export default App;
-
如果Test是函数组件,我们就可以使用mome,所以memo的作用和 PureComponent是一样的
const Test = memo((props) => { console.info("render"); return <h1>{props.name}</h1>; }) class App extends Component { state = { count: 0, } onClick = () => { const { count } = this.state; this.setState({ count: count + 1 }); } render() { console.info("red") return <div> <button onClick={this.onClick}>加1</button> <Test name={"test"} /> </div> } } export default App;
useState
-
useState可以为函数组件设置状态,传入初始化的值
class App1 extends Component { state = { count: 0, } onClick = () => { const { count } = this.state; this.setState({ count: count + 1 }); } render() { const { count } = this.state; return <div> <button onClick={this.onClick}>加1</button> <h1>{count}</h1> </div> } } function App() { const [count, setCount] = useState(0); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> </div> } export default App;
-
我们以前经常在constructor做一些初始化的值的计算 然后把props作为自己的state,useState可以接受函数 计算初始值,并且只会执行一次
function App(props) { const [count, setCount] = useState(() => { return props.defaultValue || 0 }); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> </div> } // 也可以使用对象 function App(props) { const [info, setInfo] = useState(() => { return props.defaultValue || { age: 1, name: 'test'} }); return <div> <button onClick={() => {setInfo(oldState => ({ ...oldState, age: info.age + 1 }))}}>加1</button> <h1>{info.age}{info.name}</h1> </div> }
-
useState内部也做了浅判断不会引起重复渲染
// 不会重复渲染 function App(props) { const [count, setCount] = useState(0); return <div> <button onClick={() => {setCount(count)}}>加1</button> <h1>{count}</h1> </div> } // 会重复渲染 function App(props) { const [info, setInfo] = useState(() => { return props.defaultValue || { age: 1, name: 'test'} }); return <div> <button onClick={() => {setInfo(oldState => ({ ...oldState }))}}>加1</button> <h1>{info.age}{info.name}</h1> </div> }
useEffect
-
如果使用原始的class编写监听窗口变化的组件
class App1 extends Component { state = { count: 0, size: { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, } } onResize = () => { this.setState({ size: { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, } }) } componentDidMount() { document.title = this.state.count; window.addEventListener('resize', this.onResize, false); } componentDidUpdate() { document.title = this.state.count; } componentWillUnmount() { window.removeEventListener('resize', this.onResize, false) } onClick = () => { const { count } = this.state; this.setState({ count: count + 1 }); } render() { const { count, size } = this.state; return <div> <button onClick={this.onClick}>加1</button> <h1>{count}</h1> <h2>size{JSON.stringify(size)}</h2> </div> } }
-
useEffect使得我们可以抽离之前生命周期的一些函数,用来代替componentDidMount,componentDidUpdate,componentWillUnMount,是在渲染之后执行的
-
useEffect第二个参数是数组,useEffect通过浅比较数组中的参数来决定是否执行
function App(props) { const [count, setCount] = useState(0); const [size, setSize] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); const onResize = () => { setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); } // 第二个参数是空 每次都会渲染,相当于componentDidMount,componentDidUpdate useEffect(() => { document.title = count; }); // 第二个参数是空数组,每次比较都一样所以只会执行一次 useEffect(()=> { window.addEventListener('resize', onResize, false); // 返回的参数会在的时候组件卸载的时候执行,类似于componentWillUnMount return () => { window.removeEventListener('resize', onResize, false); } }, []); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> <h2>size{JSON.stringify(size)}</h2> </div> }
useContext
-
useContext类似于contextType,但是避免了contextType只能使用一个context的问题
const myContext = createContext(); function Test() { const count = useContext(myContext) return ( <h1>{count}</h1> ) } function App(props) { const [count, setCount] = useState(0); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> <myContext.Provider value={count}> <Test/> </myContext.Provider> </div> } export default App;
useMemo/useCallback
-
useMemo是在渲染之前执行的,不同于useEffect,里面是不能写副作用的,类似于shouldComponentUpdate,但是返回值并不能控制是否渲染
-
useMemo主要是用来做优化的,下面的代码因为每次渲染onClick的引用会改变,导致Test一直渲染
const Test = memo(function(props) {
console.info('render Test')
return (
<h1>{props.count}</h1>
)
});
function App() {
const [count, setCount] = useState(0);
// 第二个参数决定useMemo是否渲染,如果不传是没意义的,每次都会执行
// 没办法做优化
const double = useMemo(() => {
return count * 2
}, [count === 1]);
const onClick = () => {
console.info('click');
}
return <div>
<button onClick={() => {setCount(count + 1)}}>加1</button>
<h1>{count}</h1>
<h1>{double}</h1>
<Test count={double} onClick={onClick}/>
</div>
}
export default App;
-
使用useMemo可以防止引用的变化,useCallBack跟useMemo效果一样的,只是少一层函数的嵌套
function App() { // setConut 的引用每次渲染都不会变 const [count, setCount] = useState(0); const double = useMemo(() => { return count * 2 }, [count === 1]); const onClick = useMemo(() => { return () => { console.info('click'); } }, []); // 也可以在里面改变State.但是需要防止死循环 useCallback(() => { setCount((count2) => count2 + 1) },[]); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> <h1>{double}</h1> <Test count={double} onClick={onClick}/> </div> } export default App;
useRef
-
useRef可以拿到类组件的ref然后执行类组件的方法
class Test extends PureComponent { select() { console.info('select') } render() { const { count, onClick } = this.props; return ( <h1 onClick={onClick}>{count}</h1> ) } } function App() { // setConut 的引用每次渲染都不会变 const [count, setCount] = useState(0); // testRef的引用不会每次都变化 const testRef = useRef(); const onClick = useMemo(() => { return () => { testRef.current.select(); } }, [testRef]); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> <Test ref={testRef} count={count} onClick={onClick}/> </div> } export default App;
-
useRef可以存储一些变量,这些变量不会产生新的引用,类似于this,例如下面的例子会导致定时器无法清除,因为time变化了
function App() { // setConut 的引用每次渲染都不会变 const [count, setCount] = useState(0); // testRef的引用不会每次都变化 let time; useEffect(() => { time = setInterval(() => { setCount(count => count + 1) }, 1000); }, []); useEffect(() => { if(count >= 10) { clearInterval(time); } }); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> </div> } export default App;
-
使用useRef修复上面的问题
function App() { // setConut 的引用每次渲染都不会变 const [count, setCount] = useState(0); // testRef的引用不会每次都变化 let time = useRef(); useEffect(() => { time.current = setInterval(() => { setCount(count => count + 1) }, 1000); }, []); useEffect(() => { if(count >= 10) { clearInterval(time.current); } }); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> </div> } export default App;
自定义hooks
-
自定义hooks函数可以复用组件的项目逻辑
function useCount(defaultCount) { // setConut 的引用每次渲染都不会变 const [count, setCount] = useState(0); // testRef的引用不会每次都变化 let time = useRef(); useEffect(() => { time.current = setInterval(() => { setCount(count => count + 1) }, 1000); }, []); useEffect(() => { if(count >= 10) { clearInterval(time.current); } }); return [count, setCount]; } function useSize() { const [size, setSize] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); const onResize = () => { setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, }); } // 第二个参数是空数组,每次比较都一样所以只会执行一次 useEffect(()=> { window.addEventListener('resize', onResize, false); // 返回的参数会在的时候组件卸载的时候执行,类似于componentWillUnMount return () => { window.removeEventListener('resize', onResize, false); } }, []); return size; } function App() { const [count, setCount] = useCount(0); const size = useSize(); return <div> <button onClick={() => {setCount(count + 1)}}>加1</button> <h1>{count}</h1> {JSON.stringify(size)} </div> }