基礎(chǔ)
- jsx(原理德召?)
- 實現(xiàn)一個簡單的createElement及render方法
- 組件聲明方式
- props和state的區(qū)別
- 綁定事件(一些特殊的事件也可以往上冒泡,如onBlur)
- 屬性校驗
- setState的使用(有兩種寫法)
- 復(fù)合組件(多個組件進行組合喻犁,父子通信槽片、子父通信的實現(xiàn))
- 受控與非受控組件
jsx表達(dá)式的用法
- 可以放JS的執(zhí)行結(jié)果
- 如果換行需要用()包裹jsx代碼
- 可以把JSX元素當(dāng)作函數(shù)的返回值
- <{來判斷是表達(dá)式還是js
jsx屬性
在JSX中分為普通屬性和特殊屬性,像class要寫成className肢础,for要寫成htmlFor style要采用對象的方式还栓, dangerouslyInnerHTML插入html
組件聲明方式
兩種,普通函數(shù)及class
屬性校驗
通常這個是會寫在一個component組件中传轰,提供給別人使用剩盒。
在react中props是組件對外暴露的接口,但通常組件并不會明顯的申明他會暴露那些接口及類型慨蛙,這不太利于組件的復(fù)用辽聊,但比較好的是React提供了PropTypes這個對象用于校驗屬性的類型,PropTypes包含組件屬性的所有可能類型期贫,以下我們通過一個示列來說明(對象的key是組件的屬性名跟匆,value是對應(yīng)屬性類型)組件屬性的校驗
class Person extends Component {
// 傳的props格式不對,不會中斷頁面渲染
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.oneOf(['男', '女']),
hobby: PropTypes.array,
// 自定義類型
salary: function (props, key, com) {
if (props[key] < 1000) {
throw new Error(`${com} error ${props[key]} is too low`)
}
},
position: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
})
}
constructor(props) {
super();
}
render() {
let { name, age, gender, hobby, salary, position } = this.props;
return (<div>
{name}{age}
{gender}{hobby}
{salary} {JSON.stringify(position)}
</div>)
}
}
受控與非受控組件
一般受控或非受控組件通砍,指的是表單元素贾铝,如input、select埠帕、checkbox等垢揩。
受控是指表單的值,必須通過事件+狀態(tài)
來改變敛瓷,比如說:
<input value="123" />
在不加onChange
事件的前提下叁巨,我們是沒法將123改為其他值的。
當(dāng)有多個表單元素時呐籽,不同的元素onChange
事件可以通過event.target.name
來做區(qū)分(前提是每個元素需要加上name
的屬性)
非受控锋勺,是指不需要通過狀態(tài)來改變,上面的代碼可以改為:
<input defaultValue="123" />
那么它在表單提交時狡蝶,怎么來獲取元素值呢庶橱?答案是通過ref
。
ref的寫法
字符串
// 在某個方法中 this.refs.username // jsx<input ref="username" />stylus
函數(shù)
// 在某個方法中 this.username // jsx<input ref={ref => this.username=ref} />verilog
對象
// 在constructor里面this.username = React.createRef();// 在某個方法中this.username.current // 這個就是dom元素// jsx<input ref={this.username} />kotlin
第一種寫法現(xiàn)在不是太推薦了贪惹,一般使用第二種或者第三種苏章,第三種需要v16.3以上的版本。
生命周期
在網(wǎng)上找到一張圖奏瞬,還是挺直觀的:
import React, { Component } from 'react';
class Counter extends React.Component {
static defaultProps = {
name: 'zpu'
};
constructor(props) {
super();
this.state = { number: 0 }
console.log('1.constructor構(gòu)造函數(shù)')
}
componentWillMount() {
console.log('2.組件將要加載 componentWillMount');
}
componentDidMount() {
console.log('4.組件掛載完成 componentDidMount');
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
};
shouldComponentUpdate(nextProps, nextState) { // 代表的是下一次的屬性 和 下一次的狀態(tài)
console.log('5.組件是否更新 shouldComponentUpdate');
return nextState.number % 2;
}
componentWillUpdate() {
console.log('6.組件將要更新 componentWillUpdate');
}
componentDidUpdate() {
console.log('7.組件完成更新 componentDidUpdate');
}
render() {
console.log('3.render');
return (
<div>
<p>{this.state.number}</p>
{this.state.number > 3 ? null : <ChildCounter n={this.state.number} />}
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
class ChildCounter extends Component {
componentWillUnmount() {
console.log('組件將要卸載componentWillUnmount')
}
componentWillMount() {
console.log('child componentWillMount')
}
render() {
console.log('child-render')
return (<div>
{this.props.n}
</div>)
}
componentDidMount() {
console.log('child componentDidMount')
}
componentWillReceiveProps(newProps) { // 第一次不會執(zhí)行枫绅,之后屬性更新時才會執(zhí)行
console.log('child componentWillReceiveProps')
}
shouldComponentUpdate(nextProps, nextState) {
console.log('child shouldComponentUpdate');
return nextProps.n % 3; // 子組件判斷接收的屬性 是否滿足更新條件 為true則更新
}
}
export default Counter;
// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 狀態(tài)更新會觸發(fā)的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 屬性更新
// componentWillReceiveProps newProps
// 卸載
// componentWillUnmount
到了react16.3,生命周期去掉了以下三個:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
This lifecycle was previously named componentWillMount. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.
上面像componentWillMount
和componentWillUpdate
去掉一般影響不會太大硼端,但是像componentWillReceiveProps
這個就有關(guān)系了并淋,所以react又新增了兩個生命周期:
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
getDerivedStateFromProps
getDerivedStateFromProps就是用來替代componentWillReceiveProps方法的,但是需要注意是它是靜態(tài)方法珍昨,在里面無法使用this县耽,它會返回一個對象作為新的state,返回null則說明不需要更新state镣典。
class Example extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
// 沒錯兔毙,這是一個static
}
}
另外它的觸發(fā)時機是:在組件構(gòu)建之后(虛擬dom之后,實際dom掛載之前) 骆撇,以及每次獲取新的props之后瞒御。和之前componentWillReceiveProps有一個區(qū)別是,后者只有獲取新的props之后神郊,才會觸發(fā)肴裙,第一次是不觸發(fā)的。
簡單例子如下:
if (nextProps.currentRow !== prevState.lastRow) {
return {
...
lastRow: nextProps.currentRow,
};
// 不更新state
return null
}
getSnapshotBeforeUpdate
文檔的意思大概是使組件能夠在可能更改之前從DOM中捕獲一些信息(例如滾動位置)涌乳。簡單地說就是在更新前記錄原來的dom節(jié)點屬性蜻懦,然后傳給componentDidUpdate。
componentDidCatch
我們都知道如果組件中有錯誤夕晓,那整個頁面可能就會變成空白宛乃,然后控制臺一堆紅色報錯。
在 React 16.x 版本中,引入了所謂 Error Boundary 的概念征炼,從而保證了發(fā)生在 UI 層的錯誤不會連鎖導(dǎo)致整個應(yīng)用程序崩潰析既;未被任何異常邊界捕獲的異常可能會導(dǎo)致整個 React 組件樹被卸載谆奥。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
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 <h1>Something Went Wrong</h1>
}
return this.props.children;
}
}
class Clock extends Component {
render() {
return (
<div>hello{null.toString()}</div>
)
}
}
class Page extends Component {
render() {
return (
<ErrorBoundary>
<Clock/>
</ErrorBoundary>
)
}
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
上下文(context api)
傳統(tǒng)寫法
父組件:
static childContextTypes={
color: PropTypes.string,
changeColor:PropTypes.func
}
getChildContext() {
return {
color: this.state.color,
changeColor:(color)=>{
this.setState({color})
}
}
}
簡單地說眼坏,就是寫兩個東西:
聲明context類型
聲明context對象,即子組件需要的context
子組件:
static contextTypes = {
color: PropTypes.string,
changeColor: PropTypes.func
}
// 使用
this.context.color;
也是先要聲明類型(這里特指需要用到的context類型酸些,如果不需要用的話宰译,就不需要聲明),然后使用this.context來取魄懂,就OK了沿侈。。
新式寫法
上面的傳統(tǒng)寫法市栗,其實是有點問題的:如果某個組件shouldComponentUpdate返回了false后面的組件就不會更新了缀拭。
當(dāng)然這個我在團隊中提及,他們有些人覺得scu返回了false肃廓,應(yīng)該是不讓它去更新了智厌,但我覺得是需要更新的。
來看看寫法吧盲赊。
// 創(chuàng)建一個消費者和提供者
let { Consumer,Provider} = React.createContext();
class Parent extends Component {
render() {
// Provider通過value來傳遞數(shù)據(jù)
return (
<Provider value={{ a: 1, b: 2 }}>
<Son></Son>
</Provider>
);
}
}
class Son extends Component {
render() {
// Consumer的children是一個函數(shù)铣鹏,函數(shù)的參數(shù)為Provider的value對象
return (
<Consumer>
{
({a, b}) => {
return (
<div>{a}, </div>
)
}
}
</Consumer>
)
}
}
寫法上哀蘑,比傳統(tǒng)的寫法更加舒服一些诚卸。當(dāng)然實際應(yīng)用中,可能會有多個Provider绘迁、多個Consumer合溺,然后嵌套,不過這樣層級寫多了缀台,比較惡心
插槽(Portals)
在react16中棠赛,提供了一個方法:
ReactDOM.createPortal(child, container)
React16.0中發(fā)布了很多新特性,我們來看portal,React提供了一個頂級API—portal膛腐,用來將子節(jié)點渲染到父節(jié)點之外的dom節(jié)點
Portals 提供了一種很好的將子節(jié)點渲染到父組件以外的 DOM 節(jié)點的方式睛约。
ReactDOM.createPortal(child, container)
第一個參數(shù)(child)是任何可渲染的 React 子元素,例如一個元素哲身,字符串或碎片辩涝。第二個參數(shù)(container)則是一個 DOM 元素。
From React文檔
import React from "react";
import { createPortal } from "react-dom";
import classnames from "classnames";
const rootEle = document.body;
/**
* show: 這個屬性通過切換類名改變樣式控制組件控制彈層的出現(xiàn)/隱藏
* onSwitch: 通過傳遞函數(shù)勘天,給予彈出層自我切換的方法
* children: react組件自帶屬性,獲取組件的開始和結(jié)束標(biāo)記之間的內(nèi)容
*/
export default ({ show, onSwitch, children }) =>
createPortal(
<div
className={classnames("modal", { "modal-show": show })}
onClick={onSwitch}
>
{children}
</div>,
rootEle
);
調(diào)用,在應(yīng)用中創(chuàng)建一個show狀態(tài)來管理彈出層的切換,以及switchModal方法用來對該狀態(tài)進行切換怔揩,并將這兩個屬性傳遞給彈出層組件
import React from "react";
import ReactDOM from "react-dom";
import Modal from "./Modal";
import "./styles.css";
class App extends React.PureComponent {
state = {
show: false
};
switchModal = () => this.setState({ show: !this.state.show });
render() {
const { show } = this.state;
return (
<div id="App">
<h1 onClick={this.switchModal}>點我彈出</h1>
<Modal show={show} onSwitch={this.switchModal}>
點擊關(guān)閉
</Modal>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
片段(fragments)
React 中一個常見模式是為一個組件返回多個元素捉邢。 片段(fragments) 可以讓你將子元素列表添加到一個分組中,并且不會在DOM中增加額外節(jié)點商膊。
舉個例子伏伐,比如說我們將一個h2元素和h3元素放到root節(jié)點去,很早之前的做法是必須要在h2和h3元素外面套一層div翘狱。但現(xiàn)在不一定非要這么做:
<React.Fragment>
<h2></h2>
<h3></h3>
</<React.Fragment>
這個也可以用在多個li返回上面秘案,當(dāng)然多個li的時候,也可以返回一個數(shù)組潦匈,加上不同的key即可,如:
const items = [
<li key="1">1</li>,
<li key="2">2</li>,
<li key="3">3</li>
]
ReactDOM.render((
<React.Fragment>
<div>aaa</div>
<h3>bb</h3>
{items}
</React.Fragment>
), document.getElementById('root'));
高階組件(HOC)
HOC赚导,全稱: Higher-Order Components茬缩。簡單地說,就是對原有的component再包裝一層吼旧,有點類似extend凰锡。
HOC(High Order Component) 是 react
中對組件邏輯復(fù)用部分進行抽離的高級技術(shù),但HOC并不是一個 React API
圈暗。 它只是一種設(shè)計模式掂为,類似于裝飾器模式。
具體而言员串,HOC就是一個函數(shù)勇哗,且該函數(shù)接受一個組件作為參數(shù),并返回一個新組件寸齐。
從結(jié)果論來說欲诺,HOC相當(dāng)于 Vue
中的 mixins(混合)
。其實 React
之前的策略也是采用 mixins
渺鹦,但是后來 facebook
意識到 mixins
產(chǎn)生的問題要比帶來的價值大扰法,所以移除了 mixins
。
Why ? 為什么使用HOC
import React, { Component } from 'react'
class Page1 extends Component{
componentWillMount(){
let data = localStorage.getItem('data')
this.setState({ data })
}
render() {
return (
<h2>{this.state.data}</h2>
)
}
}
export default Page1
這個例子中在組件掛載前需要在 localStorage 中取出 data 的值毅厚,但當(dāng)其他組件也需要從 localStorage 中取出同樣的數(shù)據(jù)進行展示的話塞颁,每個組件都需要重新寫一遍 componentWillMount 的代碼,那就會顯得非常冗余吸耿。那么在 Vue 中通常我們采用:
mixins: []
但是在 React 中我們需要采用HOC模式咯
import React, { Component } from 'react'
const withStorage = WrappedComponent => {
return class extends Component{
componentWillMount() {
let data = localStorage.getItem('data')
this.setState({ data })
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
export default withStorage
當(dāng)我們構(gòu)建好一個HOC之后祠锣,我們使用的時候就簡單多了,還看最開始的例子珍语,我們就不需要寫 componentWillMount 了锤岸。
import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'
class Page1 extends Component{
render() {
return <h2>{this.props.data}</h2>
}
}
export default withStorage(Page1)
很明顯,這是一個裝飾器模式板乙,那么就可以使用ES7形式
import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'
@withStorage
class Page1 extends Component{
render() {
return <h2>{this.props.data}</h2>
}
}
export default Page1
歡迎大家補充!