react
基本全是組件,須要使用什麼組件就安裝什麼組件。css
npm install antd -D
// 全局導入使用 import React, {Component} from 'react' import Button from 'antd/lib/button' import 'antd/dist/antd.css' export default class AntdTest extends Component { render(){ return ( <div> <Button type="primary">按鈕</Button> </div> ) } }
全局導入代碼會比較大,不利於項目開發,建議使用按需加載的方式。vue
npm install react-app-rewired@2.0.2-next.0 babel-plugin-import -D
const { injectBabelPlugin } = require('react-app-rewired'); module.exports = function override(config, env) { config = injectBabelPlugin( // 在默認配置基礎上注入 // 插件名,插件配置 ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], config ); return config }
"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }
import React, {Component} from 'react' import { Button } from 'antd' export default class AntdTest extends Component { render(){ return ( <div> <Button type="primary">按鈕</Button> </div> ) } }
基本原則:容器組件負責數據獲取,展現組件負責根據
props
展現信息
// CommentList.js import React,{Component} from 'react'; export default class CommentList extends Component { constructor(props){ super(props); this.state = { comments: [] }; } componentDidMount(){ setTimeout(() => { this.setState({ comments: [ { body: 'react is very good', author: 'facebook' }, { body: 'vue is very good', author: 'youyuxi' } ] }) },1000) } render(){ return ( <div> {this.state.comments.map((c,i) => <Comment key={i} data={c}></Comment>)} </div> ) } } // 展現組件 (傻瓜組件) class Comment extends Component { console.log('render comment') return ( <div> <p>{this.props.data.body}</p> <p>---{this.props.data.author}</p> </div> ) }
componentDidMount(){ setInterval(() => { this.setState({ comments: [ { body: 'react is very good', author: 'facebook' }, { body: 'vue is very good', author: 'youyuxi' } ] }) },1000) } class Comment extends PureComponent { render(){ // 由於數據的一直更新,其實數據是沒有變化的,render函數卻會一直執行,消耗性能 console.log('render comment') return ( <div> <p>{this.props.data.body}</p> <p>---{this.props.data.author}</p> </div> ) } }
PureComponent
(15.3出現)定製了shouldComponentUpdate
後的Component
(淺比較)
由於是淺比較,只會比較引用類型地址,只會比較一層。因此要麼傳值類型,要麼引用類型地址不能發生變化而且地址只能有一層。大體源碼解析以下:react
export default function PureComponent(props, context){ Component.call(this,props,context) } PureComponent.prototype = Object.create(Component.prototype) PureComponent.prototype.constructor = PureComponent PureComponent.prototype.isPureReactComponent = true // 着重擴展了shouldComponentUpdate的實現 PureComponent.prototype.shouldComponentUpdate = shallowCompare // 淺比較 function shallowCompare(nextProps, nextState){ return !shallowEqual(this.props,nextProps) || !shallowEqual(this.state, nextState) } export default function shallowEqual(objA, objB){ // 比較的是對象的引用,只會看引用地址是否發生變化 if(objA === objB){ return true } if(typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null){ return false } var keysA = Object.keys(objA) var keysB = Object.keys(objB) if(keysA.length !== keysB.length){ return false } // test for A's keys different from B. // 只作了一層循環 for(var i = 0; i < keysA.length; i++){ if(!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]){ return false } } return true }
import React,{Component, PureComponent} from 'react'; export default class CommentList extends Component { render(){ return ( <div> {this.state.comments.map((c,i) => ( // <Comment key={i} data={c}></Comment> // <Comment key={i} body={c.data.body} author={c.data.author}></Comment> <Comment key={i} {...c}></Comment> ))} </div> ) } } class Comment extends PureComponent { // class Comment extends Component{ render(){ console.log('render comment') return ( <div> {/* <p>{this.props.data.body}</p> <p>---{this.props.data.author}</p> */} <p>{this.props.body}</p> <p>---{this.props.author}</p> </div> ) } } // react 16.6.0版本以上擴展出一個高階組件 React.memo 實現了PureComponent // 可用該組件改寫上述代碼 const Comment = React.memo(props => { return ( <div> <p>{props.body}</p> <p>---{props.author}</p> </div> ) })
提升複用率,首先想到的就是抽離相同邏輯,在React裏就有了HOC(higher-Order Component)
的概念。
高階組件簡單講就是一個函數,接收一個組件返回一個新組件,產生的新組件能夠對屬性進行包裝,也能夠重寫部分生命週期。
// Hoc.js import React, {Component} from 'react' function Study(props){ // stage 是從父組件傳入的,name是動態獲取的 return <div>{props.stage}-{props.name}</div> } // 高階組件 const WithStudy = Comp => { // 獲取name,name可能來自接口或其餘手段 const name = '高階組件'; return props => <Comp {...props} name={name}></Comp> } const NewStudy = WithStudy(Study); export default class Hoc extends Component { render(){ return ( <div> <NewStudy stage="React"></NewStudy> </div> ) } }
// 高階組件 const WithStudy = Comp => { // 獲取name,name可能來自接口或其餘手段 const name = '高階組件'; return class NewComp extends Component { componentDidMount(){ console.log('do something') } render(){ return <Comp {...this.props} name={name}></Comp> } } // return props => <Comp {...props} name={name}></Comp> }
// Hoc.js function Study(props){ return <div>{props.stage}-{props.name}</div> } const WithStudy = Comp => { // 獲取name,name可能來自接口或其餘手段 const name = '高階組件'; // 可省略類名 return class NewComp extends Component { componentDidMount(){ console.log('do something') } render(){ return <Comp {...this.props} name={name}></Comp> } } } const widthLog = Comp => { console.log(Comp.name + '渲染了') return props => <Comp {...props}></Comp> } // 鏈式調用 const NewStudy = WithStudy(widthLog(Study)); export default class Hoc extends Component { render(){ return ( <div> <NewStudy stage="React"></NewStudy> </div> ) } }
由於鏈式語法太過累贅,es7有一個優秀的語法-裝飾器,專門處理這種問題,只能用於class
組件webpack
npm install babel-plugin-transform-decorators-legacy -D
// config-overrides.js 增長 config = injectBabelPlugin( ['@babel/plugin-proposal-decorators', { 'legacy': true }], config ) 修改了配置文件記得重啓服務
import React, {Component} from 'react' import { render } from 'react-dom'; // 高階組件 const WithStudy = Comp => { // 獲取name,name可能來自接口或其餘手段 const name = '高階組件a'; // 可省略類名 return class NewComp extends Component { componentDidMount(){ console.log('do something') } render(){ return <Comp {...this.props} name={name}></Comp> } } } const widthLog = Comp => { console.log(Comp.name + '渲染了') return props => <Comp {...props}></Comp> } // 調用執行順序從上往下,裝飾的是下面緊挨着的class @widthLog // 裝飾的是Study @WithStudy // 裝飾的是上一層包裝返回的新組件 @widthLog // 裝飾的是前兩層包裝返回的新組件 // 這裏的study就是進行修飾過的最新的study,這就是爲何高階組件代碼要放在上面 class Study extends Component { render(){ return <div>{this.props.stage}-{this.props.name}</div> } } export default class Hoc extends Component { render(){ return ( <div> <Study stage="React"></Study> </div> ) } }
Composition
組件複合給與你足夠的敏捷去定義自定義組件的外觀和行爲,並且是以一種明確而安全的方式進行。若是組件有公用的非ui
邏輯,將它們抽取爲js
模塊導入使用而不是繼承它。
// Composition.js import React from 'react'; // Dialog做爲容器,不關心內容和邏輯 // 等於vue中的slot function Dialog(props){ // children 預留屬性固定的 return <div style={{border: "4px solid blue"}}>{props.children}</div> } // WelcomeDialog經過複合提供內容 function WelcomeDialog(){ return ( <Dialog> <h1>歡迎光臨</h1> <p>感謝使用react</p> </Dialog> ) } export default function(){ return <WelcomeDialog></WelcomeDialog> }
import React from 'react'; // Dialog做爲容器,不關心內容和邏輯 // 等於vue中的slot function Dialog(props){ // children 預留屬性固定的 return <div style={{border: `4px solid ${props.color || 'blue'}`}}>{props.children}</div> } // WelcomeDialog經過複合提供內容 function WelcomeDialog(props){ return ( <Dialog {...props}> <h1>歡迎光臨</h1> <p>感謝使用react</p> </Dialog> ) } export default function(){ return <WelcomeDialog color="green"></WelcomeDialog> }
vue
,具名插槽怎麼處理?import React from 'react'; // Dialog做爲容器,不關心內容和邏輯 // 等於vue中的slot function Dialog(props){ // children 預留屬性固定的 return ( <div style={{border: `4px solid ${props.color || 'blue'}`}}> {/* 可理解爲匿名插槽 */} {props.children} <div className="footer"> {/* footer是jsx */} {props.footer} </div> </div> ) } // WelcomeDialog經過複合提供內容 function WelcomeDialog(props){ return ( <Dialog {...props}> <h1>歡迎光臨</h1> <p>感謝使用react</p> </Dialog> ) } export default function(){ const footer = <button onClick={() => alert('肯定!')}>肯定</button> return <WelcomeDialog color="green" footer={footer}></WelcomeDialog> }
children
的深層次解讀children多是jsx,也多是函數,也多是數組
一、對children做爲函數解讀web
import React from 'react'; const Api = { getUser(){ return { name: 'pj', age: 20 } } } function Fetcher(props) { const user = Api[props.name](); return props.children(user) } export default function(){ return ( <div> <Fetcher name="getUser"> {({name,age}) => ( <p> {name} - {age} </p> )} </Fetcher> </div> ) }
二、利用children是數組作濾器npm
import React from 'react'; function Filter(props){ return ( <div> {/* React.Children 提供了不少方法,而且會有異常捕獲判斷 */} {React.Children.map(props.children,child => { if(child.type !== props.type){ return } return child })} </div> ) } export default function(){ return ( <div> {/* 過濾器能夠過濾指定標籤類型 */} <Filter type="p"> <h1>react</h1> <p>react很不錯</p> <h1>vue</h1> <p>vue很不錯</p> </Filter> </div> ) }
三、修改childrenjson
import React from 'react'; // 修改children function RadioGroup(props){ return ( <div> {React.Children.map(props.children, child => { // vdom不可更改,克隆一個新的去修改 return React.cloneElement(child, {name: props.name}) })} </div> ) } function Radio({children, ...rest}){ return ( <label> <input type="radio" {...rest} /> {children} </label> ) } export default function(){ return ( <div> <RadioGroup name="mvvm"> <Radio value="vue">vue</Radio> <Radio value="react">react</Radio> <Radio value="angular">angular</Radio> </RadioGroup> </div> ) }
Hook
是React16.8
一個新增項,它可讓你在不編寫class
的狀況下使用state
以及其餘的React
特性。api
Hook
的特色// HookTest.js import React, {useState} from 'react' export default function HookTest(){ // useState(initState) 返回一個數組,索引0的位置存放的是定義的狀態,索引1的位置是改對應狀態的函數 const [count, setCount] = useState(0); return ( <div> <p>點擊了{count}次</p> <button onClick={() => setCount(count+1)}>點擊</button> </div> ) }
import React, {useState} from 'react' export default function HookTest(){ // 多個狀態 const [age] = useState(20); const [fruit, setFruit] = useState('banana'); const [input, setInput] = useState(''); const [fruits, setFruits] = useState(['apple','banana']); return ( <div> <p>年齡:{age}</p> <p>選擇的水果:{fruit}</p> <p> <input type="text" value={input} onChange={e => setInput(e.target.value)} /> <button onClick={() => setFruits([...fruits,input])}>新增水果</button> </p> <ul> {fruits.map(f => <li key={f} onClick={() => setFruit(f)}>{f}</li>)} </ul> </div> ) }
Effect Hook
useEffect
就是一個Effect Hook
,給函數組件增長了操做反作用的能力。它跟class
組件中的componentDidMount
,componentDidUpdate
和componentcomponentWillMount
具備相同的做用,只不過被合併成了一個API
.數組
import React, {useState, useEffect} from 'react' export default function HookTest(){ const [count, setCount] = useState(0); // 反作用鉤子會在每次渲染時都執行 useEffect(() => { document.title = `您點擊了${count}次` }) return ( <div> <p>點擊了{count}次</p> <button onClick={() => setCount(count+1)}>點擊</button> </div> ) }
// 若是僅打算執行一次,傳遞第二個參數爲[],相似於componentDidMount // useEffect能夠有多個,便於邏輯的拆分 useEffect(() => { // api 調用 console.log('api 調用') },[])
// 只有count發生變化,該方法纔會執行 useEffect(() => { document.title = `您點擊了${count}次` },[count]) // 若是跟多個值有關 useEffect(() => { document.title = `您點擊了${count}次` },[count,age])
Custom Hook
自定義hook
是一個函數,名稱用'use
'開頭,函數內部能夠調用其餘鉤子
import React, {useState, useEffect} from 'react' function useAge(){ const [age, setAge] = useState(0); useEffect(() => { setTimeout(() => { setAge(20) },2000) }) return age } export default function HookTest(){ const age = useAge(); return ( <div> <p>年齡:{age ? age : 'loading...'}</p> </div> ) }
Hook
useContext
、useReducer
、useCallback
、useMemo
安全
Context
上下文提供一種不須要每層設置
props
就能跨多級組件傳遞數據的方式
Context
相關API
React.createContext
Context.Provider
Class.contextType
Context.Consumer
一、第一種 消費者Consumer方式
import React from 'react' // 建立上下文 const MyContext = React.createContext(); const {Provider, Consumer} = MyContext; function Child(prop){ return ( <div> Child: {prop.foo} </div> ) } export default function ContextTest(){ return ( <div> {/* 套在Provider下,無論嵌套多少層,均可以拿到Provider上傳過來的值 */} <Provider value={{foo: 'bar'}}> {/* 第一種 消費者Consumer方式 想拿到值的組件必須被Consumer包圍 */} <Consumer> {value => <Child {...value}></Child>} </Consumer> </Provider> </div> ) }
二、消費方法二、Hook
import React, {useContext} from 'react' // 建立上下文 const MyContext = React.createContext(); const {Provider} = MyContext; // 使用Hook消費 function Child2(){ const context = useContext(MyContext); return <div>Child2: {context.foo}</div> } export default function ContextTest(){ return ( <div> {/* 套在Provider下,無論嵌套多少層,均可以拿到Provider上傳過來的值 */} <Provider value={{foo: 'bar'}}> {/* 消費方法二、Hook */} <Child2></Child2> </Provider> </div> ) }
三、消費方法三、contextType
import React, {Component} from 'react' // 建立上下文 const MyContext = React.createContext(); const {Provider} = MyContext; // 使用class指定靜態contextType class Child3 extends Component{ static contextType = MyContext; render(){ return <div>Child3: {this.context.foo}</div> } } export default function ContextTest(){ return ( <div> {/* 套在Provider下,無論嵌套多少層,均可以拿到Provider上傳過來的值 */} <Provider value={{foo: 'bar'}}> {/* 消費方法三、contextType */} <Child3></Child3> </Provider> </div> ) }
// KForm.js import React, { Component } from 'react' import {Input, Button} from 'antd' // 建立一個高階組件,擴展示有表單,事件處理,數據收集,校驗 function KFormCreate(Comp){ return class extends Component { constructor(props){ super(props); this.options = {}; this.state = {}; } handleChange = e => { const {name, value} = e.target; this.setState({ [name]: value }, ()=>{ // 確保值發生變化再校驗 this.validateField(name); }) } // 單項校驗 validateField = field => { // 一、獲取校驗規則 const rules = this.options[field].rules; // 任意一項失敗則返回false const ret = !rules.some(rule => { if(rule.required){ if(!this.state[field]){ // 校驗失敗 this.setState({ [field + 'Message']: rule.message }) return true } } }) // 校驗成功 if(ret){ this.setState({ [field + 'Message']: '' }) } return ret } // 校驗全部字段 validate = cb => { const rets = Object.keys(this.options).map(field => this.validateField(field) ); const ret = rets.every( v => v == true); cb(ret,this.state) } // 建立input包裝器 getFieldDec = (field,option) => { // 保存當前輸入項配置 this.options[field] = option; return InputComp => ( <div> {React.cloneElement(InputComp, { name: field, value: this.state[field] || '', onChange: this.handleChange })} {/* 校驗錯誤信息 */} {this.state[field + 'Message'] && ( <p style={{color: 'red'}}>{this.state[field + 'Message']}</p> )} </div> ) } render(){ return <Comp getFieldDec={this.getFieldDec} validate={this.validate}></Comp> } } } @KFormCreate class KForm extends Component { onSubmit = () => { // 校驗全部項 this.props.validate((isValid, data) => { if(isValid){ // 提交登陸 console.log('登陸',data) // 後續登陸邏輯 }else { alert('校驗失敗') } }); } render(){ const {getFieldDec} = this.props; return ( <div> {getFieldDec('uname',{ rules: [ { required: true, message: '用戶名必填' } ] })(<Input></Input>)} {getFieldDec('pwd',{ rules: [ { required: true, message: '密碼必填' } ] })(<Input type="password"></Input>)} <Button onClick={this.onSubmit}>登陸</Button> </div> ) } } export default KForm