React16.8的新特性及舊特性


首先簡單談談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數據。

兩者的選取:
若需要表單即時驗證,選擇受控組件,不需要即時驗證,提交時驗證,則可以選擇非受控組件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM