首先简单谈谈react和vue的区别:
如果你写过vue,会发现组件的视图指令已编译为修改视图的函数存放在绑定的state里的属性里,所以能够做到靶向修改,而react会以组件为根,重新渲染整个组件子树。所以应避免这些不必要的render。
0、setState和shouldComponentUpdate
setState特性:
1、setState是异步操作函数,很多时候,我们需要想要的state状态更新完成后再进行某些操作。此时,我们可以选择在componentWillUpdate生命周期或者componentDidUpdate生命周期的回调函数去执行我们的操作。虽然也可以达到预期效果,但是这样做不是最佳方法,代码变得破碎,可读性也不好。
因此,此时我们就需要保证setState的同步更新。
有两种方案:
通过回调函数
通过async/await来异步转同步
2、setState会造成不必要的渲染
3、setState并不能很有效的管理所有的组件状态
默认情况下仅对React事件处理程序内的更新进行批处理。
如:
class Container extends React.Component { constructor(props) { super(props); this.state = { a: false, b: false }; } render() { return <Button onClick={this.handleClick}/> } handleClick = () => { this.setState({ a: true }); // setState 是不保证同步的所以打印的state依然是旧的,可以在setState中写回调函数得到改变后的state console.log(this.state) this.setState({ b: true }, ( )=> { console.log(this.state) }); } } // 不会出现只改变b的情况 class SuperContainer extends React.Component { constructor(props) { super(props); this.state = { a: false }; } render() { return <Container setParentState={this.setState.bind(this)}/> } } class Container extends React.Component { constructor(props) { super(props); this.state = { b: false }; } render() { return <Button onClick={this.handleClick}/> } handleClick = () => { this.props.setParentState({ a: true }); this.setState({ b: true }); } }
在类组件中,如果一个li发生变化,那么整个ul都会重新渲染。所以子组件变化,会导致父组件整个发生重新渲染。调用 setState 方法总是会触发 render 方法从而进行 vdom re-render 相关逻辑,哪怕实际上你没有更改到 Component.state。
为了避免这种性能上的浪费,React 提供了一个 shouldComponentUpdate 来控制触发 vdom re-render 逻辑的条件。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
注:后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。
1、函数组件:
特点:没有自身的状态、无生命周期。
//没有自身的状态,相同的props输入必然会获得完全相同的组件展示。不需要关心组件的一些生命周期函数和渲染的钩子更简洁。
import React, { Component } from "react"; const Button = ({ day }) => { return ( <div> <button className="btn btn-warning">我是 {day}</button> </div> ); }; class Greeting extends Component { render() { return <Button day="纯函数组件"></Button>; } } export default Greeting;
使用场景:
一些只渲染一次的纯展示的场景,比如一个列表、表格等。
2、纯组件(pureComponent):
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。除非 shouldComponentUpdate() 返回 false,否则 setState() 将始终执行重新渲染操作。首次渲染或使用 forceUpdate() 时不会调用该方法。所以pureComponent就相当于是一个写好shouldComponentUpdate的类组件。
和类组件的区别:自带渲染性能优化(shouldComponentUpdate)
但是:React.PureComponent 通过props和state的浅对比来实现 shouldComponentUpate()
如果对象包含复杂的数据结构,它可能会因深层的数据不一致而产生错误的否定判断(表现为对象深层的数据已改变视图却没有更新)
解决方法:
注:但是无论是普通组件还是pure纯组件,发生状态变化时候,如果state不是一个普通对象,shouldComponentUpdate就无法拦截复杂数据结构的数据变化,因为地址没有变,比如push数据到一个数组,新的数组和旧的数组指向同一个地址,无法比较是否变化,这时就要借助immutable。
**immutable把变化的节点加上原来的老节点,返回一个指向新地址的新树(新对象)。类似深拷贝。**如之前:{a: 1, b: 2},之后:{a: 1, b: 3},这样shouldComponentUpdate就可以继续比较这两个对象了。
//demo1: PureComponent的自动为我们添加的shouldComponentUpate函数 import React, { PureComponent } from "react"; class CounterButton extends PureComponent { constructor(props) { super(props); this.state = { count: 1 }; } render() { return ( <button className="btn btn-info" onClick={() => this.setState(state => ({ count: state.count + 1 }))} > Count: {this.state.count} </button> ); } } export default CounterButton;
// pure组件只是对类组件的升级,没有解决掉复杂对象无法比较的问题,还是要引入immutable。 // demo2 import React from "react"; const { List } = require("immutable"); // let data = []; //创建不可变的对象 let data = List(["start"]); class CounterButton extends React.Component { constructor(props) { super(props); this.state = { count: data }; } render() { return ( <button color={this.props.color} onClick={() => { // data.push(Math.random()); this.setState(state => ({ count: this.state.count.push(Math.random()) })); }} > {/* Count: {this.state.count.length} */} Count: {this.state.count.size} </button> ); } } export default CounterButton;
应用场景:
如果组件的内部状态改变的很频繁,并且不想染外部一起渲染,就使用pureComponent,它内部会自动计算并拦截状态的变化,不用自己去维护shouldComponentUpdate函数了。
3、高阶组件
用于对普通组件进行包装,并扩展功能。
高阶函数:
// demo1 // hoc为高阶函数 function hello (){ console.log("?我是高阶组件") } function hoc(fn){ // 关键是要返回一个函数,不能执行 return ()=>{ console.log("first"); fn(); console.log("end"); } } const hocresult = hoc(hello); hocresult(); // demo2 function welcome(username) { console.log('welcome ' + username); } function goodbey(username) { console.log('goodbey ' + username); } //高阶函数 function wrapWithUsername(wrappedFunc) { let newFunc = () => { let username = localStorage.getItem('username'); wrappedFunc(username); }; return newFunc; } // eslint-disable-next-line no-func-assign 一般不能重新定义函数 welcome = wrapWithUsername(welcome); // eslint-disable-next-line no-func-assign goodbey = wrapWithUsername(goodbey); welcome(); goodbey();
高阶组件:
用函数包裹,函数参数接受一个普通组件,并最终返回一个新组件,这个返回的新组件就叫做高阶组件
/
/=========高阶组件的实战代码demo1===== import {Component} from 'react' function HOCFactoryFactory(...params){ return function HOCFactory(WrappedComponent){ return class HOC extends Component{ render(){ return <WrappedComponent {...this.props} /> } } } } //使用方式1,注入 @HOCFactoryFactory({}) class WrappedComponent extends React.Component{} //使用方式2 HOCFactoryFactory({})(WrappedComponent) //=========高阶组件的实战代码demo2===== //##高阶组件之后的代码 //注值 localStorage.username = "老袁" const wrapWithUsername = WrappedComponent => { class NewComponent extends Component { constructor() { super(); this.state = { username: "" }; } componentWillMount() { let username = localStorage.getItem("username"); this.setState({ username: username }); } render() // 这里重新包装了传入的普通组件,并交给新生成的高阶组件去渲染 return <WrappedComponent username={this.state.username} />; } } return NewComponent; }; class Welcome extends Component { render() { return <div className="text-warning">welcome {this.props.username}</div>; } } // 把Welcome升级成高阶组件 Welcome = wrapWithUsername(Welcome); class Goodbye extends Component { render() { return <div className="text-info">goodbye {this.props.username}</div>; } } //升级高阶组件 Goodbye = wrapWithUsername(Goodbye); class Greeting extends Component { render() { return ( <> <Welcome /> <Goodbye /> </> ); } } export default Greeting;
应用场景:redux
4、插槽(Portals组件)
Portals 提供了一个顶级的方法,使得我们有能力把一个子组件渲染到父组件 DOM 层级以外的 DOM 节点上。
使用:ReactDOM.createPortal()方法
import React from 'react' import ReactDOM from 'react-dom' import "./component.css" //组件插槽 const portalElm = document.createElement('div'); portalElm.className="txtcenter" document.body.appendChild(portalElm) class App extends React.Component { state = { show: true, } handleClick = () => { this.setState({ show: !this.state.show, }) } render() { return ( <div> <button className="btn btn-primary" onClick={this.handleClick}>动态展现Portal组件</button> {this.state.show ? ( <div>{ReactDOM.createPortal(<span>Portal组件</span>, portalElm)}</div> ) : null} </div> ) } } export default App
应用场景:弹窗
5、处理异步数据或组件(suspense)
react支持用suspense去处理异步数据,不需要async/await。 //新增了render 新的返回类型:fragments 和 strings import React, { Suspense, lazy } from "react"; import "./suspense.css"; // import { useFetch } from "react-hooks-fetch"; // console.log("异步加载数据", useFetch); //动态加载组件 const LazyComp = lazy(() => import("./lazy")); function fetchApi() { const promise = new Promise(resolve => { setTimeout(() => { resolve("Data resolved"); }, 3000); }); return promise; } //创建Fetcher var cached = {}; const createFetcher = promiseTask => { let ref = cached; return () => { const task = promiseTask(); task.then(res => { ref = res; }); console.log("?--ref",ref); console.log("?--cached",cached); if (ref === cached) { throw task; } //得到结果输出 console.log("?",ref); return ref; }; }; const requestData = createFetcher(fetchApi); function SuspenseComp() { // const {error,data} = useFetch("a.php"); // console.log("数据?",data) // if (error) return <span>出错了/(ㄒoㄒ)/~~</span>; // if (!data) return null; // return <span>RemoteData:{data.title}</span>; const data = requestData(); return <p className="text-warning">{data}</p>; } export default () => ( <Suspense fallback={<div className="text-danger">loading<i></i></div>}> <SuspenseComp /> <LazyComp /> </Suspense> );
6、memo组件
React.memo() 是高阶函数能将函数组件转换成类似于React.PureComponent组件。
//React.memo() 是高阶函数能将函数组件转换成类似于React.PureComponent组件 import React, { memo, Component } from "react"; function Child({ seconds }) { console.log("I am rendering"); return <div>Memo组件 seconds->{seconds} </div>; } function areEqual(prevProps, nextProps) { if (prevProps.seconds === nextProps.seconds) { return true; } else { return false; } } // const RocketComponent = props => <div>my rocket component. {props.fuel}!</div>; // 创建一个只在prop改变时发生渲染的版本 // const MemoizedRocketComponent = memo(RocketComponent); // const memocom = () => { // return memo(Child, areEqual); // }; const DemoComponent = memo(Child, areEqual); class Greeting extends Component { render() { return <DemoComponent seconds="20" />; } } export default Greeting; // function Child({seconds}){ // console.log('I am rendering'); // return ( // <div>I am update every {seconds} seconds</div> // ) // }; // export default React.memo(Child)
7、context API
Context 主要是解决props向多层嵌套的子组件传递的问题,原理是定义了一个全局对象。
子节点要用Consumer包裹
//Context 主要是解决props向多层嵌套的子组件传递的问题,原理是定义了一个全局对象 import React from "react"; import PropTypes from "prop-types"; const { Provider, Consumer } = React.createContext("default"); class Parent extends React.Component { state = { yideng: "普通字符串?", newContext: "京程一灯" }; // getChildContext() { // return { value: this.state.newContext, yideng: this.state.yideng }; // } render() { // <React.Fragment> == <> return ( <> <div> <label className="text-warning">父节点=> newContext:</label> <input type="text" value={this.state.newContext} onChange={e => this.setState({ newContext: e.target.value })} /> </div> <div> <label className="text-info">父节点=>yideng:</label> <input type="text" value={this.state.yideng} onChange={e => this.setState({ yideng: e.target.value })} /> </div> {/* {this.props.children} */} <Provider value={{ newContext: this.state.newContext, yideng: "普通字符串?" }} > {this.props.children} </Provider> </> ); } } function Child(props, context) { return ( <Consumer> {value => ( <p className="text-warning">子节点=> newContext: {value.newContext}</p> )} </Consumer> ); } class Child2 extends React.Component { static contextTypes = { yideng: PropTypes.string }; render() { // return <p>字符串a: {this.context.yideng}</p>; return ( <Consumer> {value => <p className="text-info">子节点=> yideng: {value.yideng}</p>} </Consumer> ); } } Child.contextTypes = { value: PropTypes.string }; // Parent.childContextTypes = { // value: PropTypes.string, // yideng: PropTypes.string // }; export default () => ( <Parent> <Child /> <Child2 /> </Parent> );
8、ref
react采用了新的ref方式,使用React.createRef()
forwardRef,省去了ref复杂程度。
// demo1 import React from 'react' const TargetComponent = React.forwardRef((props, ref) => ( <input type="text" ref={ref} /> )) export default class Comp extends React.Component { constructor() { super() this.ref = React.createRef() } componentDidMount() { this.ref.current.value = '转发ref成功?' } render() { return <TargetComponent ref={this.ref} /> } } // demo2 import React from 'react' export default class RefDemo extends React.Component { constructor() { super() this.objRef = React.createRef(); } componentDidMount() { setTimeout(() => { this.refs.stringRef.textContent = 'string ref got' this.methodRef.textContent = 'method ref got' this.objRef.current.textContent = 'obj ref got' }, 30) } render() { return ( <> <p className="text-success" ref="stringRef">span1</p> <p ref={ele => (this.methodRef = ele)}>span3</p> <p ref={this.objRef}>span3</p> </>)}}
8、error组件
增加了componentDidCatch生命周期,父组件捕捉错误
import React, { Component } from "react"; class ErrorBoundary extends Component { constructor(props) { super(props); this.state = { hasError: false }; } // 捕捉错误和错误上报程序库一起使用 componentDidCatch(err, info) { this.setState({ hasError: true }); } render() { if (this.state.hasError) { return <div>Something went wrong!</div>; } return this.props.children; } } class Profile extends Component { constructor(props) { super(props); this.state = { }; } render() { return <span>用户名:{this.state.user.push(1)}</span> } } class Greeting extends Component { render() { return ( <ErrorBoundary> <Profile/> </ErrorBoundary> ); } } export default Greeting;
9.1 shouldComponentUpdate若返回false不会重新渲染渲染
9.2 可以使用this.setState()的生命周期:
componentWillmount()
componentWillReceiveProps()
componentDidMount()
componentDidUpdate()
9.3 react16废弃的生命周期:
componentWillMount
componentWillReceiveProps
componentWillUpdate
新增生命周期:
getSnapshotBeforeUpdate
getDerivedStateFromProps代替componentWillReceiveProps
getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
用途:因为 “render” 阶段生命周期(如 render)和 “commit” 阶段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之间可能存在延迟。
10、hooks
hooks的诞生是为了解决react开发中遇到的问题
1、this的指向问题。
2、生命周期。
3、原本的函数组件是空的,给函数组件扩展功能。
/** * 1.只能在函数组件中使用hooks * 2.函数组件业务变更无需修改成class组件 * 3.告别了繁杂的this和难以记忆的生命周期 * 4.合并的生命周期componentDidMount、componentDidUpdate、和 componentWillUnmount * 5.包装自己的hooks 是基于纯命令式的api * 6.更好的完成状态之间的共享 解决原来class组件内部封装问题。也解决了高阶组件和函数组件的嵌套过深 * 7.useReducer集成redux * 8.useEffect接受脏操作等到react更新了DOM之后,它再依次执行我们定义的副作用函数。这里就是一个io且是异步的 */ /** * 以上我们学习过的方法均提供了ref * useState 返回有状态值,以及更新这个状态值的函数 * useEffect 接受包含命令式,可能有副作用代码的函数。 * useContext 接受上下文对象(从React.createContext返回的值)并返回当前上下文值, * useReducer useState的替代方案。接受类型为(state,action) => newState的reducer,并返回与dispatch方法配对的当前状态。 * useCallback 返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性 * useMemo 纯的一个记忆函数 * useRef 返回一个可变的ref对象,其.current属性被初始化为传递的参数 * useImperativeMethods 自定义使用ref时公开给父组件的实例值 * useMutationEffect 更新兄弟组件之前,它在React执行其DOM改变的同一阶段同步触发 * useLayoutEffect DOM改变后同步触发。使用它来从DOM读取布局并同步重新渲染 */ import React, { useState, useEffect } from "react"; const useCount = (initialCount = 0) => { const [count, setCount] = useState(initialCount); return [count, () => setCount(count + 1), () => setCount(count - 1)]; }; export default () => { const [count, increment, decrement] = useCount(1); //首次渲染完成 // componentDidMount() { // document.title = `You clicked ${this.state.count} times`; // } //更新渲染完成 // componentDidUpdate() { // document.title = `You clicked ${this.state.count} times`; // } //组件卸载阶段 == return function useEffect每次组件变更均执行 // componentWillUnmount(){ // } useEffect(() => { console.log("component update"); document.title = `标题-${count} times`; return () => { console.log("unbind"); }; }, [count]); return ( <> <input type="button" value="增加count" onClick={increment} /> <span>当前count: {count}</span> <input type="button" value="减少count" onClick={decrement} /> </> ); };
11、受控组件和非受控组件
在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。
受控组件:
在 HTML 中,表单元素(如<input>、 <textarea> 和 <select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。输入的表单数据保存在组件的state属性中,并且只能通过使用 setState()来更新。使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('提交的名字: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> 名字: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="提交" /> </form> ); } }
非受控组件:
动态数据交给dom节点处理,借助ref读取dom数据。
两者的选取:
若需要表单即时验证,选择受控组件,不需要即时验证,提交时验证,则可以选择非受控组件。