學(xué)做網(wǎng)站學(xué)什么語(yǔ)言最成功的網(wǎng)絡(luò)營(yíng)銷案例
在React中,V16版本之前有三種方式創(chuàng)建組件( 被刪除了),之后只有兩種方式創(chuàng)建組件。這兩種方式的組件創(chuàng)建方式效果基本相同,但還是有一些區(qū)別,這兩種方法在體如下:createClass()
本節(jié)先了解下用extnds React.Component 的方式創(chuàng)建React組件。
通過(guò)繼承React.Component創(chuàng)建React組件
以下是一個(gè)簡(jiǎn)單的模板實(shí)現(xiàn):
<script type="text/babel">//組件名稱要用大寫字母開(kāi)頭,因?yàn)镽eact在渲染時(shí)會(huì)區(qū)分大小寫。class Suggest extends React.Component { //自定義組件static defaultProps ={//定義組件參數(shù)默認(rèn)值color: 'blue',text: 'Confirm',}; //state是一個(gè)屬性,所以要寫分號(hào)結(jié)尾constructor(props) { //定義構(gòu)造函數(shù),不需要寫function關(guān)鍵字super(props); //必須要先調(diào)用父類的構(gòu)造函數(shù),否則無(wú)法使用this關(guān)鍵字this.state = { //定義state,一個(gè)屬性,React組件的MVVM渲染就是造它來(lái)完成的//name: "korgs"}; } //方法結(jié)尾沒(méi)有逗號(hào)render() { //UI渲染方法return (<> {/*定義JSX最外層元素*/}<div> 我是顯示內(nèi)容 </div></>);}}const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<Suggest text="我是參數(shù)值"/>); //組件調(diào)用
</script>
核心屬性
props
props是properties的縮寫。props是React用來(lái)讓組件之間互相聯(lián)系的一種機(jī)制,通俗地說(shuō)就是方法的參數(shù),組件的props一定來(lái)自于默認(rèn)屬性或通過(guò)父組件傳遞而來(lái),在組件中通過(guò)this.props
來(lái)獲取。
render() { //UI渲染return (<><div>{this.props.text}</div></>);}//調(diào)用示例,這個(gè)值會(huì)覆蓋掉組件中static defaultProps定義默認(rèn)的值root.render(<Suggest text="我是參數(shù)值"/>);
props在React是單向數(shù)據(jù)流,值也是不可變的。當(dāng)試圖改變props的原始值時(shí),React會(huì)報(bào)出類型錯(cuò)誤的警告,一般可以通過(guò)復(fù)制的方法來(lái)操作:
var attribs = Object.assign({}, this.props); // 淺復(fù)制
const obj = {...this.state.obj, foo:'bar'}; //淺復(fù)制另一種寫法(推薦)
propTypes
非必要實(shí)現(xiàn),它會(huì)列出所有可傳遞給組件的參數(shù)列表,同時(shí)也提供了參數(shù)驗(yàn)證功能,但注意,它只會(huì)在控制臺(tái)發(fā)出警告,并不會(huì)阻斷程序運(yùn)行,在V16之后已從React核心庫(kù)中刪除了,所以需要單獨(dú)安裝:
npm i --save-dev prop-types
import PropsTypes from "prop-types"class Suggest extends React.Component { //組件static defaultProps ={color: 'blue',text: 'Confirm',};
}Suggest.props = { //參數(shù)列表color: PropsTypes.string.isRequired,
}export default Suggest
以下是一些常見(jiàn)的用法:
Suggest.propTypes = {type: PropTypes.oneOf(['year', 'suggest', 'rating', 'text', 'input']),id: PropTypes.string,options: PropTypes.array,defaultValue: PropTypes.any,children: React.PropTypes.oneOfType([React.PropTypes.arrayOf(React.PropTypes.node),React.PropTypes.node,]),
};
ref
如果需要訪問(wèn)底層DOM節(jié)點(diǎn)來(lái)執(zhí)行一些命令式操作時(shí),可以使用ref
屬性,這個(gè)屬性值一般要求全局唯一,有兩個(gè)術(shù)語(yǔ)需要先分清:
- 組件:指React組件;
- DOM節(jié)點(diǎn):指組件渲染后的html的Dom節(jié)點(diǎn),通過(guò)組件可以拿到DOM節(jié)點(diǎn);
class Focus extends React.Component {constructor(props) {super(props)this.handleClick = this.handleClick.bind(this)}handleClick() {this.element.focus()}render() {return (<div>{/*這里的ref是一個(gè)回調(diào)函數(shù),組件加載時(shí)就會(huì)運(yùn)行*/}<input type="text" ref={element => (this.element = element)} /><button onClick={this.handleClick}>Focus</button></div>)}
}
需要特別注意
this.element = element
中的element
指向的組件,而非DOM節(jié)點(diǎn)元素,比如上面例子中this.element = <input type="text" />
,這樣如果是個(gè)復(fù)雜組件,還可以訪問(wèn)組件內(nèi)部的方法。
核心方法
render()
React在組件更新并非每次整個(gè)區(qū)域全部重新渲染,React會(huì)在內(nèi)部維護(hù)一個(gè)稱為Virual DOM的虛擬樹(shù),每次有DOM更新時(shí)就會(huì)維護(hù)此虛擬樹(shù)結(jié)構(gòu),然后在需要UI重繪區(qū)域采用DOM DIFF
算法計(jì)算出最小改變,然后選擇最佳性能方法,比如append還是insert等方法重繪。
render() { //UI渲染return (<><div> 我是顯示內(nèi)容 </div></>);
}
render() 在實(shí)現(xiàn)上一般只返回組件,即使只是一段文本,也建議要用JSX的形式進(jìn)行包裝。
添加樣式
內(nèi)部樣式
在組件內(nèi)編寫樣式代碼,React的style不能用字面量,需要封裝成一個(gè)對(duì)象。然后需要用駝峰式代替原來(lái)的-
連接符。
render() { //UI渲染const styleClass = {color:`red',background:`url({imgUrl})`,height:10 //默認(rèn)是px};return (<><div style={styleClass}>css</div></>);
}
外部樣式
正常使用時(shí)可以定義一個(gè).css外部樣式文件,然后在.js中引入。就可以正常使用了。
import './index.css'
使用classnames插件實(shí)現(xiàn)動(dòng)態(tài)樣式
npm i --save-dev classnames
,這個(gè)插件主要用于當(dāng)需要?jiǎng)討B(tài)給組件添加樣式,減少if語(yǔ)句數(shù)量用的,比如,下面代碼就實(shí)現(xiàn)了按鍵顏色的切換。
import React from 'react'
import classnames from 'classnames';
import "./all.css"class ClassNameDemo extends React.Component {constructor(props) {super(props);this.state ={isPressed: false,};this.handleClick = this.handleClick.bind(this);}handleClick() {this.setState({isPressed: true})}render() {//使用方式const btnClass = classnames({'btn':true,'btn-pressed': this.state.isPressed,})return (<div><input type="text"/><button className={btnClass} onClick={this.handleClick}>Focus</button></div>)}}export default ClassNameDemo
all.css文件容
.btn{}
.btn-pressed{color: #f3f;
}
事件交互
React把所有事件綁定到結(jié)構(gòu)的最外層,使用一個(gè)統(tǒng)一的事件監(jiān)聽(tīng)器來(lái)處理(事件委派機(jī)制)。
React基于Virtual DOM實(shí)現(xiàn)了一個(gè)SyntheticEvent(合成事件層)
,所有自定義的事件都會(huì)接收到一個(gè)兼容的SyntheticEvent對(duì)象實(shí)例,它與原生的Event完全兼容,同樣也可以使用stopPropagation()
和preventDefault()
來(lái)中斷。
如果需要使用原生Event,可以調(diào)用SyntheticEvent.nativeEvent
屬性。
綁定事件
- 構(gòu)造器內(nèi)綁定:這種不能傳遞參數(shù),好處是性能好,只bind一次即可**(推薦)**
class Suggest extends React.Component {static defaultProps ={ };constructor(props) {super(props);//綁定this到事件中,以實(shí)現(xiàn)在自定義事件中的this指向組件,而非事件this.handleClick = this.handleClick.bind(this);}handleClick(e){/*一般在此處會(huì)調(diào)用this.setState({})方法重繪UI界面有時(shí)為了防止冒泡,會(huì)調(diào)用 e.preventDefault() 方法*/console.log(e.target); //target = <button>Click me!!!</button>}render() { //UI渲染return (<><button onClick={this.handleClick}>Click me!!!</button></>);}}
- 自動(dòng)綁定:這種可以傳遞參數(shù),但每次渲染都要重新綁定一次
<script type="text/babel">class App extends React.Component {constructor(props) {super(props);this.state = {inputVal: 'male',};}handleInputChange(args, e) {console.log(e);console.log(args);}render() {const { inputVal} = this.state;return (<div><input type="radio" value="male" checked={inputVal === 'male'} onClick= {this.handleInputChange.bind(this,"dd")}/></div>);}}const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<App text="我是參數(shù)值"/>);
</script>
原生事件
一些特殊的事件React并沒(méi)有實(shí)現(xiàn),所以在必要時(shí)還會(huì)需要使用js的原生的事件機(jī)制,通過(guò)ref定義通過(guò)refs取得組件DOM
,然后在組件加載后綁定原生的偵聽(tīng)事件,代碼如下:
class Suggest extends React.Component { static defaultProps ={ };constructor(props) {super(props); this.handListener = this.handListener.bind(this);}componentDidMount(){//refs在新版本中已經(jīng)不建議再用了,而且本身也不建議再使用了this.refs.buttonDom.addEventListener('click', this.handListener);}handListener(e){console.log(e.target);}render() { //UI渲染return (<><button ref="buttonDom">Click buttonDom!!!</button></>);}}
事件處理技巧
主要是為了防止綁定的事件太多,在一個(gè)地方綁定,然后用swith來(lái)分發(fā),示例如下:
class Button extends React.Component {constructor(props) {super(props)this.handleEvent = this.handleEvent.bind(this) //綁定一個(gè)事件}handleEvent(event) {switch (event.type) { //根據(jù)事件類型分發(fā)事件case 'click':console.log('clicked')breakcase 'dblclick':console.log('double clicked')breakdefault:console.log('unhandled', event.type)}}render() {return (<button onClick={this.handleEvent} onDoubleClick={this.handleEvent}>Click me!</button>)}
}
遠(yuǎn)程獲取數(shù)據(jù)
從遠(yuǎn)程URL獲取數(shù)據(jù),下例展示了一個(gè)從遠(yuǎn)程獲取組件state初始化數(shù)據(jù)的過(guò)程。
componentDidMount() {const _this = this; //指向組件 this,否則在fetch函數(shù)中無(wú)法引用組件fetch('https://api.github.com/users/grearon/gists', {method: 'POST',mode: 'no-cors',headers: {'Content-Type': 'application/x-www-form-urlencoded'},// body: JSON.stringify(response)}).then(function(response) {_this.setState({response}); //重置state}).catch( error => console.log(error));}
導(dǎo)出和導(dǎo)入
可以直接寫成下面這樣的代碼,因?yàn)槲覀兊淖谥际菢?gòu)建小而美的組件,所以下列的代碼完全合理:
//Gallery.js
export default function Gallery(){ }
export function Profile(){ }
語(yǔ)法 | 導(dǎo)出語(yǔ)句 | 導(dǎo)入語(yǔ)句 |
---|---|---|
默認(rèn) | export default function Button() {} | import Button from ‘./Button.js’; |
具名 | export function Button() {} | import { Button } from ‘./Button.js’; |
- 最后的.js可以省略
- 同一文件中,有且僅有一個(gè)默認(rèn)導(dǎo)出,但可以有多個(gè)具名導(dǎo)出!
組件調(diào)用
添加子元素
除了像下面代碼這樣調(diào)用組件:
const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<Suggest text="我是參數(shù)值"/>); //組件調(diào)用
還可以在調(diào)用時(shí)傳入子組件,比如組件這樣來(lái)定義:
return (<><div style={styleClass}>{this.props.children}</div></>
);
調(diào)用
const container = document.getElementById('app');
const root = ReactDOM.createRoot(container);
root.render(<Suggest text="我是參數(shù)值"><div>ddd</div></Suggest>
);
注意看上例中{this.props.children}
這個(gè)聲明,它表示組件調(diào)用時(shí)可傳遞任意數(shù)量的子節(jié)點(diǎn)到組件中,上例中與<div>ddd</div>
對(duì)應(yīng)。
組件間通信
主要就是用到了props和state功能,希望兩個(gè)組件的狀態(tài)始終同步更改。要實(shí)現(xiàn)這一點(diǎn),可以將相關(guān) state 從這兩個(gè)組件上移除,并把 state 放到它們的公共父級(jí),再通過(guò) props 將 state 傳遞給這兩個(gè)組件。
父向子
父向子通過(guò)this.props
屬性,注意看下面的代碼實(shí)現(xiàn)
<script type="text/babel">//子組件,定義了一個(gè)名為name的屬性class Child extends React.Component{static defaultProps = {name:"button"}render(){console.log(this.props);return (<button>{this.props.name}</button>);}}//父組件class App extends React.Component{constructor(props){super(props);}render(){return (<>{/* 采用props傳值給子組件,這時(shí)子組件其實(shí)會(huì)有兩個(gè)屬性值 */}<Child {...this.props}/> </>);}}const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<App name="我是參數(shù)值" color="red"/>);
</script>
子向父
子組件控制父組件主要是通過(guò)回調(diào)函數(shù)實(shí)現(xiàn),在父組件中定義回調(diào)方法,在子組件中注冊(cè)事件,然后父組件把回調(diào)方法以參數(shù)的方式傳遞給子組件。
<script type="text/babel">class Child extends React.Component{static defaultProps = {name:"button",}render(){return (<button onClick={this.props.btnClick}>{this.props.name}</button>);}}//父組件class App extends React.Component{constructor(props){super(props);//定義一個(gè)數(shù)據(jù),供子組件來(lái)修改this.state={count:0}this.handleClick = this.handleClick.bind(this);}//定義回調(diào)函數(shù)handleClick(e) {this.setState({count: this.state.count+1})console.log(e.target + this.state.count); //~~out: <button>我是參數(shù)值</button>0}render() {return (<> {/*回調(diào)函數(shù)以參數(shù)的形式傳給子組件*/}<Child btnClick={this.handleClick} {...this.props}/></>);}}const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<App name="我是參數(shù)值" color="red"/>);
</script>
同級(jí)間
一般會(huì)采用公有父組件的方式,說(shuō)白了就是向上組裝,由父組件統(tǒng)一控制state數(shù)據(jù),以實(shí)現(xiàn)數(shù)據(jù)共享的目的,或是再向上包裝或封裝成一個(gè)單獨(dú)的類對(duì)象。
<script type="text/babel">//子組件,定義了一個(gè)名為name的屬性class Child extends React.Component{static defaultProps = {name:"button",}render(){return (<button onClick={this.props.btnClick}>{this.props.name}</button>);}}//子組件Displayconst Display = ({counter}) => {return <h1>{counter}</h1>}//父組件class App extends React.Component{constructor(props){super(props);//定義一個(gè)數(shù)據(jù),供子組件來(lái)修改this.state={count:0}this.handleClick = this.handleClick.bind(this);}handleClick(e) {this.setState({count: this.state.count+1})console.log(e.target + this.state.count);}render() {return (<><Child btnClick={this.handleClick} {...this.props}/><Display counter={this.state.count}/>{/*共享state數(shù)據(jù)*/}</>);}}const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<App name="我是參數(shù)值" color="red"/>);
</script>
-------- 以上組件的基本用法全介紹完了,下面是一個(gè)常用的實(shí)例---------
例子:處理表單數(shù)據(jù)
表單封裝
<script type="text/babel">class UserForm extends React.Component {constructor(props) {super(props);this.state = {userName:"",userAge:0,};this.handleSubmit = this.handleSubmit.bind(this);this.handleContentChange = this.handleContentChange.bind(this);}handleSubmit(event){console.log("提交操作");console.log(this.state);}/*注意這種綁定方法的實(shí)現(xiàn)*/handleContentChange(event){//用這種方法可減少change函數(shù)的個(gè)數(shù)this.setState({[event.target.name]: event.target.value,})}render() {const {color, text} = this.props;return (<><form onSubmit={this.handleSubmit}>{/*注意這種綁定方法*/}<label>用戶名:</label><input name="userName" type="text" value={this.state.name} onChange={this.handleContentChange}/><button className={`btn btn-${color}`} >提交</button></form></>);}}const container = document.getElementById('app');const root = ReactDOM.createRoot(container);root.render(<UserForm />);
</script>
使用schema插件自動(dòng)創(chuàng)建表單
下面的例子只是列出了如何格式化表單,至于jsonschema的詳細(xì)用法可查看其官網(wǎng),https://react-jsonschema-form.readthedocs.io/en/v1.8.1/。
安裝 npm install --save react-jsonschema-form
class JSONSchemaForm extends React.Component {constructor() {super();this.handleSubmit = this.handleSubmit.bind(this);}handleSubmit = (values) => {console.log(values);}render() {//構(gòu)建form表單const schemaForm = {type:"object",properties: {firstName: {type:"string", default:"Dan"},lastName: {type:"string", default:"Abramov"},}}return (<Form schema={schemaForm} onSubmit={this.handleSubmit}><button type="submit">Sub2mit</button></Form>)}
}
例子:組件優(yōu)化
剝離操作和展現(xiàn)
就是把UI定義成一個(gè)方法、變量、參數(shù)或是單獨(dú)組件,舉例如下:
const ui = [<><div> 我是顯示內(nèi)容 </div><div className="Suggest" ref="div01">{this.props.text}</div><div style={styleClass}>css</div></>
]return (ui
);
展開(kāi)屬性…的用法
...
用法其實(shí)就是一種語(yǔ)法糖,比如:
render() { //UI渲染const attr = {href:"www.baidu.com",alt:"_blank"}return (<> {/* 這種寫法其實(shí)就是herf={attr.href} alt={attr.alt}*/}<a {...attr}>click</a></>);
}
刪除屬性
// 淺復(fù)制
var attribs = Object.assign({}, this.props);
//這段代碼等價(jià)于: delete attribs.size;
var {size, ...attribs} = this.props;
Immutable不可變數(shù)據(jù)集
這個(gè)庫(kù)定義了多個(gè)類型工具類,如List, Stack, Map等,其主要是為了函數(shù)編程使用
cnpm insatll --save-dev immutable
import {List} from 'immutable';
//因?yàn)閕mmutable中的對(duì)象和原生js比較相像,所以盡量用命名區(qū)分開(kāi),以免混淆
let $$list = List([1, 2]);
$$list.push(3, 4);let $$map = Immutable.Map({a:1, b:2, c:3})
Pure-render處理shouldComponentUpdate
這一節(jié)內(nèi)容可以和Immutable不可變數(shù)據(jù)集一同應(yīng)用,因?yàn)閟tate變化會(huì)自動(dòng)render(),所以在操作state前最好使用Immutable把一些數(shù)據(jù)變成不可變的,由人為來(lái)判斷是否需要改變數(shù)據(jù)。
也就是在某此時(shí)刻最好把state看成是一個(gè)不可變對(duì)象。
cnpm i --save-dev react-addons-pure-render-mixin
import PureRenderMixin from 'react-addons-pure-render-mixin'class App extends React.Component{constructor(props) {super(props);//一個(gè)淺復(fù)制的應(yīng)現(xiàn),代替原來(lái)的should實(shí)現(xiàn)this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);}render(){return <div>aaa</div>}
}