1、為什麼元件化
什麼是react
-
React 是一個用於構建使用者介面的JavaScript庫
-
核心專注於檢視,目的實現元件化開發
元件化
的概念 我們可以很直觀的將一個複雜的頁面分割成若干個獨立元件,每個元件包含自己的邏輯和樣式 再將這些獨立元件組合完成一個複雜的頁面。 這樣既減少了邏輯複雜度,又實現了程式碼的重用
-
可組合:一個元件可以和其他的元件一起使用或者可以直接巢狀在另一個元件內部
-
可重用:每個元件都是具有獨立功能的,它可以被使用在多個場景中
-
可維護:每個小的元件僅僅包含自身的邏輯,更容易被理解和維護
自己實現一個元件化 — 開關例子
// 原生js實現
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Switch</title>
</head>
<body>
<div class="app">
<div>
<input type="checkbox" class="switch">
<p class="text">關</p>
</div>
<div>
<input type="checkbox" class="switch">
<p class="text">關</p>
</div>
</div>
<script>
let btn = document.querySelector(".switch");
let text = document.querySelector(".text");
btn.addEventListener('change',function (e) {
text.innerHTML = e.target.checked?'開':'關'
},false)
</script>
<style>
.switch{
-webkit-appearance: none;
width: 50px;
border: 1px solid #dfdfdf;
border-radius: 30px;
height: 32px;
position: relative;
outline: none;
/*background: #7264ff;*/
transition: all 0.2s linear;
}
.switch:checked{
box-shadow:#7264ff 0 0 16px 16px inset;
transition: all 0.2s linear;
}
.switch:before{
content: '';
position: absolute;
left: 0;
top:0;
width: 30px;
height: 30px;
border-radius: 50%;
box-shadow: 1px 1px 2px 0 #dfdfdf;
background: #ffffff;
transition:all 0.2s linear;
}
.switch:checked:before{
left: 20px;
transition:all 0.2s linear;
}
</style>
</body>
</html>
複製程式碼
由於上面的例子:js和html不能複用,所以建立字串(複用html結構)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Switch</title>
</head>
<body>
<div class="app">
</div>
<script>
// 建立字串(複用html結構)
class Switch{
render(){
return (`
<div>
<input type="checkbox" class="switch">
<p class="text">關</p>
</div>
`)
}
}
let app = document.querySelector('.app');
app.innerHTML = new Switch().render();
app.innerHTML += new Switch().render();
</script>
<style>...</style>
</body>
</html>
複製程式碼
但是,字串不能繫結事件,所以我們要將字串變換成dom元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Switch</title>
</head>
<body>
<div class="app">
</div>
<script>
// 將字串變換成dom元素
class Switch{
// 申明一個私有狀態 控制開關
constructor(){
this.state = {turn:false}
}
// 建立dom元素 接收引數是字串,將字串轉成dom
createDOMFromString(str){
let oDiv = document.createElement('div');
oDiv.innerHTML = str;
// 獲取第一個兒子節點
return oDiv.firstElementChild;
}
// 改變狀態,從新渲染dom
setState(newState){
// Object.assign:後面的覆蓋前面的
Object.assign(this.state,newState);
// this.state={...this.state,...{turn:!this.state.trun}};
let oldEl = this.el;
let newEl = this.render();// 渲染一個新的元素
oldEl.parentNode.replaceChild(newEl,oldEl); // 將老的替換成新的
}
Change(){ // this就是例項
// console.log(this)
this.setState({turn:!this.state.turn});
}
render(){
this.el = this.createDOMFromString(`
<div>
<input type="checkbox" class="switch"
${this.state.turn?'checked':''}>
<p class="text">${this.state.turn?'開':'關'}</p>
</div>
`);
this.el.firstElementChild.addEventListener('change',this.Change.bind(this),
false);
// 這時這個el就是個dom元素 可以綁事件
return this.el;
}
}
let app = document.querySelector('.app');
app.appendChild(new Switch().render());
app.appendChild(new Switch().render());
</script>
<style>...</style>
</body>
</html>
複製程式碼
封裝方法,將公用的方法提取出來
<script>
class Component{
createDOMFromString(str){
let oDiv = document.createElement('div');
oDiv.innerHTML = str;
return oDiv.firstElementChild;
}
// 如果想渲染dom 可以通過setState
setState(newState){
Object.assign(this.state,newState);
let oldEl = this.el;
let newEl = this._render();
oldEl.parentNode.replaceChild(newEl,oldEl);
}
_render(){
// this是new Switch()例項,this.render()拿到字串
this.el = this.createDOMFromString(this.render());
this.el.firstElementChild.addEventListener('change',this.Change.bind(this),false);
return this.el;
}
// 把元素新增到頁面上
mount(container){
container.appendChild(this._render());
}
}
// 繼承Component
class Switch extends Component{
constructor(){
super();
this.state = {turn:false}
}
Change(){ // this就是例項
setTimeout(()=>{
this.setState({turn:!this.state.turn});
},300)
}
render(){
return `
<div>
<input type="checkbox" class="switch"
${this.state.turn?'checked':''}>
<p class="text">${this.state.turn?'開':'關'}</p>
</div>
`
}
}
let app = document.querySelector('.app');
// app.appendChild(new Switch()._render());
// app.appendChild(new Switch()._render());
// 模擬實現了一個渲染的render方法
// new Switch().mount(app);
let render = (ele,container)=>{
ele.mount(container);
};
render(new Switch(),app);
render(new Switch(),app);
</script>
複製程式碼
2、createElement+render
建立react專案
生成react專案的create-react-app,只在命令列裡用的 -g
npm install create-react-app -g
create-react-app <project-name>
複製程式碼
開啟專案,生成的專案可以自動監控改動進行重新整理
cd <project-name>
npm strat
複製程式碼
預設會自動安裝React,react由兩部分組成,分別是:
-
react.js 是 React 的核心庫
-
react-dom.js 是提供與DOM相關的功能,會在window下增加ReactDOM屬性,內部比較重要的方法是render,將react元素或者react元件插入到頁面中。
配置
eslint,jshint關閉驗證程式碼是否符合規範,在default setting中設定
js版本es6 選擇React jsx ——> 在default setting中設定找到language
環境作用
public下有一個index.html
src下要保證有一個index.js
最後會將檔案打包到html中
寫個hello world
react有兩部分組成,一個叫react包,react-dom,語法都是es6
import語法要放置到頁面最頂部
ReactDOM中就一個方法比較常用 叫render
react元素,JSX元素 javascript+xml html也是xml的一種 javascript+html
jsx的html部分和原生html"基本"一樣,不是完全一樣
// index.js
import React from 'react';
// {render} 將ReactDOM.render()進行解構後可直接使用render()
import ReactDOM,{render} from 'react-dom';
// react有兩部分組成,一個叫react包,react-dom,語法都是es6
// import語法要放置到頁面最頂部
// ReactDOM中就一個方法比較常用 叫render
// react元素,JSX元素 javascript+xml html也是xml的一種 javascript+html
// jsx html部分和原生html"基本"一樣,不是完全一樣
render(<h1>hello world</h1>,document.getElementById('root'));
複製程式碼
jsx是一個語法糖,babeljs.io這個網站可以進行轉義
簡介JSX
是一種JS和HTML混合的語法,將元件的結構、資料甚至樣式都聚合在一起定義元件,會編譯成普通的Javascript。
需要注意的是JSX並不是html,在JSX中屬性不能包含關鍵字,像class需要寫成className,for需要寫成htmlFor,並且屬性名需要採用駝峰命名法!
createElement
jsx元素->React.createElement->虛擬dom物件->render方法
import React from 'react';
import ReactDOM,{render} from 'react-dom';
// 1、JSX其實只是一種語法糖,最終會通過babel轉譯成createElement語法
// React.createElement(
// 'h1',
// { className: 'red' },
// '\u55E8\uFF0C',
// React.createElement(
// 'span',
// { id: 'handsome' },
// '\u7F8E\u5973'
// )
// );
console.log(<h1 className='red'>嗨,<span id='handsome'>美女</span></h1>);
// 2、將react元素轉化成一個物件"虛擬dom" {type:"h1",props:{className:"red",children:[嗨,
// {type:"span",props:{id:"handsome",children:"帥哥"}]}}
// 3、通過render方法渲染出一個物件,真實dom
render(<h1 className='red'>嗨,<span id='handsome'>美女</span></h1>,
document.getElementById('root'));
複製程式碼
createElement簡單實現程式碼
// react元素/JSX元素
function ReactElement(type,props){ // type,props
this.type = type;
this.props = props;
}
function createElement(type,props,...children){
if(children.length === 1) children = children[0];
// children要放到props裡面去
return new ReactElement(type,{...props,children:children});
}
// 模擬render實現
// {type:"h1",props:{className:"red",children:[嗨,
// {type:"span",props:{id:"handsome",children:"帥哥"}]}}
let myRender = (obj,container) =>{
let {type,props} = obj;
let ele = document.createElement(type);// 建立第一層
// key代表className和children
for(let key in props){
if(key === 'children'){
// children有可能是陣列 也可能是一個
if(typeof props[key] === 'object'){// 陣列 ["姜,",{type:'',props:{}}]
props[key].forEach(item=>{
if(typeof item === 'object'){
// 如果子元素是物件那就繼續呼叫myRender
myRender(item,ele)
}else{
ele.appendChild(document.createTextNode(item));
}
})
}else{ // 一個的話直接插入到h1中
ele.appendChild(document.createTextNode(props[key]));
}
}else if(key === 'className'){
ele.setAttribute('class',props[key]);
}else{
ele.setAttribute(key,props[key]);
}
}
container.appendChild(ele);// 將元素插入到頁面中
};
// 將我們自己建立出來的"元素"物件 插入到root中
myRender(createElement(
'h1',
{ className: 'red' },
'\u55E8\uFF0C',
createElement(
'span',
{ id: 'handsome' },
'\u7F8E\u5973'
)
),document.getElementById('root'));
複製程式碼
3、元件
jsx的語法規則
1、在react中想將js當作變數引入到jsx中需要使用{}
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
let str = '哈嘍';
let el = <span>{str}</span>;
ReactDOM.render(el,document.getElementById('root'));
複製程式碼
2、在jsx中,相鄰的兩個jsx元素 ,渲染時需要外面包裹著一層元素
3、{}取值表示式,取的是有返回值的結果, 可以放JS的執行結果
4、如果多個元素想在return後面換行,我們需要加一個()當作整體返回
5、<{來判斷當前是html還是js
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
function build(str) {
return (
<div>
{/*這是註釋*/}
<h1>{str.name}建立學校</h1>
<h1>{str.name}建立學校</h1>
</div>
)
}
// let el = <div>{build('哈嘍')}</div>;
let el = <div>{build({name:'哈嘍'})}</div>;
ReactDOM.render(el,document.getElementById('root'));
複製程式碼
迴圈陣列的列子
迴圈時需要帶key屬性
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
let lessons = [
{name:'vue',price:800},
{name:'react',price:1000},
];
function toLesson(item) {
return `當前課程是${item.name} 價格是${item.price}`
}
// 陣列方法 find map filter reduce
let ele = (
<ul>
{lessons.map((item,index)=>(
// null在react中也是一個合法的元素 表示不存在,沒有
item.price<1000?null:<li key={index}>{toLesson(item)}</li>
))}
</ul>
);
ReactDOM.render(ele,window.root);
// window.root 直接取id
複製程式碼
jsx中的屬性規則
-
在JSX中分為普通屬性和特殊屬性,像class要寫成className,for要寫成htmlFor
-
style要採用物件的方式
-
dangerouslyInnerHTML插入html(xss攻擊,基本上用不到)
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
let str = '<h1>純標籤</h1>';
let styl = {backgroundColor:'red'};
render(<ul>
<li id='cc' className='aa bb'></li>
<li htmlFor="aa" style={styl}></li>
<li dangerouslySetInnerHTML={{__html:str}}></li>
</ul>,window.root)
複製程式碼
react元件的特點宣告方式
react元素是是元件組成的基本單位
-
首字母必須大寫,目的是為了和JSX元素進行區分
-
元件定義後可以像JSX元素一樣進行使用
-
每個元件必須返回唯一的頂級JSX元素
-
可以通過render方法將元件渲染成真實DOM
元件的兩種定義方式
react怎麼區分是元件還是jsx元素?元件名需要開頭大寫,react元件當作jsx來進行使用
- 第一種方式是函式宣告
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
let school1 = {name:'張三',age:8};
let school2 = {name:'李四',age:0};
function Build(props) {
return <p>{props.name}{props.age}</p>
}
render(<div>
{/*<Build name={school1.name} age={school1.age}/>*/}
{/*<Build name={school2.name} age={school2.age}/>*/}
{/*將物件中的內容解構出來傳遞給Build元件 不用一個個取出來傳遞*/}
<Build name={...school1}/>
<Build name={...school2}/>
</div>,window.root)
複製程式碼
- 第二種方式是類宣告
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import PropTypes from 'prop-types';// 屬性校驗的包
// 1、屬性是由外界傳遞的,外面不能改屬性,只有狀態是屬於元件自己的
class School extends Component{ // Component提供了列如this.setState()方法
static propType = { // 靜態屬性 es7,es6只支援靜態函式
age:PropTypes.string
};
static defaultProps = { // 校驗預設屬性
age:'100'
};
// 類上有自己的建構函式constructor(),super()子類繼承父類私有屬性,extends繼承的是公有
constructor(props){
super(props);
}
render(){ // render元件長什麼樣子,render的返回值只能有一個根元素
return(
// 通過{}取值不能列印物件 可以轉化成字串列印
<div>
{JSON.stringify(this.props)}
</div>
)
}
}
// School.prototype = {age:PropTypes.string};
// render將虛擬dom裝換成真實dom
render(<School name={'珠峰'} age={8}/>,window.root);
複製程式碼
宣告元件的方式有兩種:函式宣告(名字開頭必須大寫,沒有生命週期、狀態、this)和類宣告(有生命週期: componentDidMount 渲染完成,componentWillUnmount 元件將要解除安裝,有狀態和this)
4、屬性應用
元件中屬性和狀態的區別
元件的資料來源有兩個地方
-
props 外界傳遞過來的(預設屬性,屬性校驗)
-
state 狀態是自己的,改變狀態唯一的方式就是setState
屬性和狀態的變化都會影響檢視更新
改變state狀態例子
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
class Counter extends Component{
constructor(){
super();
this.state = {count:{number:1}} // state 狀態是自己的
};
handleClick = () =>{
this.setState({ // 改變狀態唯一的方式就是setState
count:{number:this.state.count.number+1}
});
};
render(){
return(
<p>
{this.state.count.number}
<button onClick={this.handleClick}>+</button>
</p>
)
}
}
// 呼叫了兩次counter元件 複合元件
ReactDOM.render(<div>
<Counter/>
<Counter/>
</div>,window.root);
複製程式碼
-
什麼是複合元件,就是將多個元件進行組合,例如呼叫了兩次counter元件(見上面程式碼列子)
-
解構非常複雜時可以把元件分離(見下面程式碼例子)
-
複合元件,有父子關係,父的資料傳遞給子的資料(見下面程式碼列子)
通過屬性傳遞資料例子 => 父傳子
實現一個Panel元件, Panel(父元件) Header(子元件) Body(子元件) 三個元件
專案下安裝bootstrap yarn add bootstrap,import引入css
yarn需要先npm i yarn -g全域性安裝一下
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
// react是單向資料流 資料傳遞是先通過屬性傳遞傳給父,再通過屬性傳遞由父元件傳給子元件
class Panel extends Component{
render(){
let {header,body} = this.props;
return(
<div className='container' style={{color:'red'}}>
<div className='panel-default panel'>
<Header header={header}/>
<Body body={body}/>
</div>
</div>
)
}
}
class Header extends Component{
render(){
return(
<div className='panel-heading'>{this.props.header}</div>
)
}
}
class Body extends Component{
render(){
return(
<div className='panel-body'>{this.props.body}</div>
)
}
}
let data = {header:'我非常帥',body:'長的帥'};
ReactDOM.render(<Panel {...data}/>,window.root);
複製程式碼
-
父傳子,通過屬性傳遞值(上訴例子)
-
子傳父,父親通過屬性change傳遞給兒子一個函式,兒子呼叫父親的函式,將值傳遞給父親,父親再更新值,重新整理檢視(例子如下)
子傳父
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
class Panel extends Component{
constructor(){
super();
this.state = {color:'primary'}
}
changColor = (color) =>{// 到時候兒子傳遞一個顏色
this.setState({color:color});
}
render(){
return(
<div className='container' style={{color:'red'}}>
<div className={'panel-'+this.state.color+' panel'}>
<Header
header={this.props.header}
change={this.changColor}
/>
</div>
</div>
)
}
}
class Header extends Component{
handleClick = () =>{
this.props.change('danger');
}
render(){
return(
<div className='panel-heading'>
{this.props.header}
<button
className='btn btn-danger'
onClick={this.handleClick}>
改顏色
</button>
</div>
)
}
}
let data = {header:'我非常帥'};
ReactDOM.render(<Panel {...data}/>,window.root);
複製程式碼
5、受控元件和非受控元件
受控元件(受狀態控制)和非受控元件
受控元件
-
受狀態金控制的元件,必須要有onChange方法,否則不能使用
-
受控元件可以賦予預設值(官方推薦使用:受控元件)
受控元件-輸入框例子1
輸入框和檢視用同一個資料(這個資料不能是外界傳過來的,是內部自己的,宣告一個私有狀態 ),用於檢視和輸入框,這樣輸入框改檢視就重新整理
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
// 輸入框和檢視用同一個資料(這個資料不能是外界傳過來的,是內部自己的,宣告一個私有狀態
// ),用於檢視和輸入框,這樣輸入框改檢視就重新整理
class Input extends Component{
constructor(){
super();
this.state = {val:'100'}
}
handleChange = (e) =>{//e是事件源,e.target獲取元素
let val = e.target.value;//獲取到輸入框的值
this.setState({val});
}
render(){
return(
<div>
<input type="text" value={this.state.val}
onChange={this.handleChange}/>
{this.state.val}
</div>
)
}
}
ReactDOM.render(<Input/>,window.root);
複製程式碼
受控元件-輸入框例子2-求和
輸入框value值和檢視用同一個資料,由於這個資料不能是外界傳遞過來的,所以只能宣告一個私有狀態state,通過函式handleChange(key,e)去改變這個狀態,key表示的就是當前狀態改的是哪一個,e表示的是事件源
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
class Sum extends Component{
constructor(){
super();
this.state = {a:1,b:1};
}
// key表示的就是當前狀態改的是哪一個,e表示的是事件源
handleChange(key,e){// 處理多個輸入框的值對映到狀態的方法
// key寫在setState中表示是物件的key,物件裡只有a,b。所以[key]相當於一個變數會把key
// 對應的值a和b取出來
this.setState({
[key]:e.target.value
})
}
render(){
return(
<div>
<input type="text"
value={this.state.a}
onChange={(e)=>{this.handleChange("a",e)}}
/>
<input type="text"
value={this.state.b}
onChange={e=>{this.handleChange("b",e)}}
/>
{parseInt(this.state.a)+ parseInt(this.state.b)}
</div>
)
}
}
ReactDOM.render(<Sum/>,window.root);
複製程式碼
非受控元件-輸入框列子-求和
不受受狀態控制的元件,不可以賦予預設值,通過ref設定的屬性,可以通過this.refs獲取到對應的dom元素
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
// 輸入框value值不受狀態控制,不能初始化預設值
class Sum extends Component{
constructor(){
super();
this.state = {result:''}
}
// 通過ref設定的屬性 可以通過this.refs獲取到對應的dom元素
handleChange = () =>{
let result = this.refs.a.value + this.b.value;
this.setState({result});
};
render(){
return(
<div onChange={this.handleChange}>
<input type="text" ref="a"/>
{/*x代表的是真實的dom,把元素掛載在了當前例項上,上面好能拿到這個dom元素*/}
<input type="text" ref={ x => this.b = x }/>
{this.state.result}
</div>
)
}
}
ReactDOM.render(<Sum/>,window.root);
複製程式碼
6、搜尋框
環境安裝
1、https://www.cnblogs.com/whycxb/p/7126116.html 谷歌瀏覽器中安裝JsonView擴充套件程式,方便檢視jsonp,抓包地址
2、https://github.com/kugouxiaohuang/react-developer-tools-chrome安裝react-developer-tools-chrome3、需要安裝jsonp(通過jsonp這個包去傳送請求實現跨域) https://github.com/webmodules/jsonp,jsonp安裝使用
yarn add jsonp
複製程式碼
百度搜尋框例子
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import oldJSONP from 'jsonp';
// jsonp不支援promise,所以寫個新的方法 能支援promise也能支援jsonp
function jsonp(url,opts={}) {
return new Promise((resolve,reject)=>{
// url是jsonp請求的路徑 opts是請求的屬性,第三個引數是成功的回撥
oldJSONP(url,opts,function (err,data) {
if(err)reject(err);
resolve(data);// 成功呼叫resolve
})
})
}
class Search extends Component{
constructor(){
super();
this.state = {val:'',arr:[],index:-1}//val 是輸入框的內容 arr是所有資料列表
}
// // 頁面載入完後就調這個方法(看效果的)
// // async + await await跟的是promise 有await就需要用async來修飾此函式
// async componentDidMount(){
// // let result = await jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a',{param:'cb'});
// let result = await jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+wd,{param:'cb'});
// console.log(result)
// }
handleChange = async (e) => {
let wd = e.target.value;
this.wd = wd;//儲存輸入的內容
// let {s} = result; 這裡的s是從jsonp返回值裡解構出來的
// jsonp(url,opts,fn) 百度回撥名字叫cb,
let {s} = await jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+wd,{param:'cb'});
this.setState({val:wd,arr:s});
// console.log(this.state.arr);
}
changeIndex = (e) => {
let index = this.state.index;// 預設的索引
if(e.keyCode===38 || e.keyCode===40){
e.preventDefault();//組織預設行為 避免游標錯位
if(e.keyCode===38){
index--;
if(index === -2){ // 目前在-1 --變成了-2 回到最後一個
index = this.state.arr.length-1;
}
}else {
index++;
if(index === this.state.arr.length){ //當越界了 回到-1
index = -1;
}
}
this.setState({index:index,val:this.state.arr[index] || this.wd });
}
}
enter = (e) =>{
if(e.keyCode === 13){
window.open('https://www.baidu.com/s?wd='+this.state.val)
}
}
render(){
return(
<div className='container'>
<div className='panel panel-default'>
<div className='panel-heading'>
<input type="text"
className='form-control'
value={this.state.val}
onChange={this.handleChange}
onKeyDown={this.changeIndex}
onKeyUp={this.enter}
/>
</div>
<div className='panel-body'>
<ul className='list-group'>
{this.state.arr.map((item,index)=>{
// console.log(this.state.arr);
return <li className={(this.state.index === index?'' + 'active':'')+' list-group-item'} key={index}>{item}</li>
// map需要返回一個新陣列 記得這裡要加return
})}
</ul>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<Search/>,window.root);
複製程式碼
7、生命週期(必問必會)
計數器例子
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
// 生命週期流程圖:defaultProps —> constructor —> componentWillMount —> render
// —> componentDidMount (改狀態的時候,會從新渲染)
class Counter extends Component{
// // PureComponent會比較兩個狀態相等就不會重新整理檢視,是淺比較
// class Counter extends React.PureComponent{
// 靜態屬性
static defaultProps = {
name:'珠峰培訓'
}
constructor(props){
super();
this.state = {number:0}
console.log('1.constructor建構函式')
}
// 元件將要掛載
componentWillMount(){
// // localStorage.getIte()取本地的資料
// // (同步的方式:採用渲染之前獲取資料,值只渲染一次)
// localStorage.getItem('a'); // (同步)
console.log('2.元件將要載入 componentWillMount');
}
componentDidMount(){
console.log('4.元件掛載完成 componentDidMount');
}
//通過handleClick函式改變狀態
handleClick =() => {
// this.setState({number:this.state.number});
this.setState({number:this.state.number+1});
}
// react可以shouldComponentUpdate方法中優化,或者通過PureComponent(純元件) 可以幫我們做這件事
// shouldComponentUpdate是否應該更新,返回boole型別,預設true,如果此函式返回false,就不會呼叫render方法了
shouldComponentUpdate(nextProps,nextState){ // 代表的是下一次的屬性和下一次的狀態
console.log('5.元件是否跟新 shouldComponentUpdata')
// console.log(nextState);
// return false;
return nextState.number%2; // 奇數跟新偶數不跟新
// return nextState.number !== this.state.number;//如果狀態沒有變就不跟新(優化,狀態沒變繼續跟新耗效能)
} // 不要隨便用setState 可能會死迴圈
componentWillUpdate(){
console.log('6.元件將要跟新 componentWillUpdate')
}
componentDidUpdate(){
console.log('7.元件完成跟新 componentDidUpdate')
}
render(){
console.log('3.render');
return(
<div>
<p>{this.state.number}</p>
<ChildCounter n={this.state.number}/>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
// 再寫個子元件 看看屬性的變化(以上是狀態的變化的生命週期),把父元件的狀態變成屬性傳遞給兒子,兒子通過props拿到這個屬性
// 先渲染父元件的render,再渲染子元件的render。注意:componentWillMount和componentDidMount只走一次
// 只要是元件就會有生命週期 同樣ChildCounter也會有生命週期
class ChildCounter extends Component{
componentWillMount(){
console.log('child componentWillMount')
}
render(){
console.log('child-render')
return(
<div>
{this.props.n}
</div>
)
}
componentDidMount(){
console.log('child componentDidMount')
}
// 元件將要接收新的屬性,引數是最新的屬性,第一次不會執行,之後屬性更新時才會執行
componentWillReceiveProps(newProps){
console.log('child componentWillReceiveProps')
}
shouldComponentUpdate(nextProps,nextState){
return nextProps.n%3; // 這時候子元件判斷接收的屬性,是否滿足跟新條件 為true則更新
}
}
ReactDOM.render(<Counter name='計數器'/>,window.root);
複製程式碼
整個流程走勢圖如下:
首先頁面渲染
點按鈕,狀態+1,滿足奇數更新偶數不更新,先渲染父元件的render,再渲染子元件的render,頁面中父子元件值都變成1。 注意:componentWillMount和componentDidMount只走一次再點按鈕。狀態再+1,這時候值變成了2,但是由於偶數不更新,所以頁面中的父子元件值還是是1
再點按鈕,這時候值為3,父元件跟新但是子元件接收到新的屬性不跟新,所以父元件值為3,子元件值不跟新為1。(依次類推)
8、留言板
頁面元件化
評論的皮膚有多少個元件(儘可能把邏輯拆分,好維護):最外面的容器MessageBox、訊息表單MessageForm、訊息列表MessageList。訊息列表中每條資訊資料也可以複用,寫成一個元件MessageItem。所以一共四個元件,最後把這四個元件在index.js中進行整合。如圖:
程式碼列子
index.js
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import MessageBox from "./MessageBox";
import 'bootstrap/dist/css/bootstrap.css'
// 一般整合的檔案 只需要渲染
// 根元件 我們將它進行渲染即可
ReactDOM.render(<MessageBox/>,window.root);
複製程式碼
MessageBox.js
-
PureComponent純元件,比較狀態的地址,地址相同不會更新。
-
通過父元件狀態來進行互動
-
傳遞給後代元件只能一級一級的傳遞,不能跨級傳遞。
-
子不能改父,所以父親通過屬性add傳遞給兒子一個函式,兒子呼叫父親的函式,將值傳遞給父親,父親再更新值,重新整理檢視
-
componentWillMount頁面一載入獲取localStorage資料渲染,避免重新整理頁面資料也丟失
import React,{Component} from 'react';
import MessageList from "./MessageList";
import MessageForm from "./MessageForm";
// export default匯出MessageBox元件,index.js引用並渲染
// export default class MessageBox extends React.Component {
// PureComponent純元件,比較狀態的地址,地址相同不會更新。由於push返回的是一個原陣列
// 所以這裡如果使用PureComponent,將不會更新渲染,所以這裡一般不建議使用push而是最好採用用新的狀態
// 覆蓋掉老的(也算是優化了 不然呼叫shouldComponentUpdate(nextProps,nextState)去比較更麻煩)
export default class MessageBox extends React.PureComponent {
// 通過父元件狀態來進行互動
constructor(){
super();
// this.state = {messages:[{id:1,content:'今天吃藥了嗎?',auth:'珠峰培訓',createAt:Date.now()}]}
this.state = {messages:[]}
}
// 根據id進行刪除,傳遞給後代元件只能一級一級的傳遞,不能跨級傳遞。這裡通過delete,del去傳遞的
deleteMessage = (id) => {
let messages = this.state.messages.filter(item => item.id != id);
this.setState({ // 重新設定狀態,這個方法是非同步的
messages:messages
})
// 新增完或者刪除完 存下資料
localStorage.setItem('messages',JSON.stringify(messages));
}
// 子不能改父,所以父親通過屬性add傳遞給兒子一個函式,兒子呼叫父親的函式,
// 將值傳遞給父親,父親再更新值,重新整理檢視
addMessage = (message) =>{ // message是兒子傳遞過來的,實現子父傳遞
// let messageItem = {...message,id:this.state.messages.length,createAt:Date.now()}
let messageItem = {...message,id:Math.random(),createAt:Date.now()}
// this.state.messages.push(messageItem);// 不會更新渲染資料,只有呼叫setStateat才會更新,push不會改變原有的引用地址
// this.setState({
// messages:this.state.messages
// });// 放到狀態中
let messages = [...this.state.messages,messageItem];
this.setState({
messages
});// 放到狀態中
// console.log(this.state.messages)
// 新增完或者刪除完 存下資料
localStorage.setItem('messages',JSON.stringify(messages));
}
// 1、當頁面一載入獲取localStorage資料渲染,避免重新整理頁面資料也丟失
// 2、生命週期是同步的 在componentWillMount中setState是同步的
// 3、取localStorage的值時 取到後放在狀態中 再進行render,執行一次,因為
// willMount中的setState會和this.state狀態合併。如果這裡是DitMount會渲染兩次
componentWillMount(){
let messages = JSON.parse(localStorage.getItem('messages')) || [];
this.setState({messages:messages})
}
render(){
return (
<div className='container'>
<div className='row'>
<div className='col-md-6 col-md-offset-3'>
<div className='panel panel-danger'>
<div className='panel-heading'>
<h1 className='text-center h3'>留言板</h1>
</div>
<div className='panel-body'>
<MessageList messages={this.state.messages} delete={this.deleteMessage}/>
</div>
<div className='panel-footer'>
<MessageForm add={this.addMessage}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
複製程式碼
MessageForm.js
- 通過ref獲取input的value值,然後呼叫父元件通過屬性add傳遞過來的addMessage方法,把獲取到的值通過props.add()傳遞到父元件,父元件再通過addMessage方法去從新設定狀態值(setState),頁面就會從新渲染(只有當頁面狀態改變或者屬性改變時,頁面才會從新渲染)
import React,{Component} from 'react';
export default class MessageForm extends React.Component {
handleSubmit = (e) =>{
e.preventDefault();//阻止預設行為,這裡是阻止表單的提交
let message = {auth:this.auth.value,content:this.content.value};
// console.log(message);// 看拿到這個值沒有
this.props.add(message);// 呼叫父元件的方法 將值傳遞到父元件中
}
render(){
return (
<form action="form" onSubmit={this.handleSubmit}>
<div className='form-group'>
<label htmlFor="auth" className='control-label'>留言人</label>
<input type="text" id='auth' className='form-control' ref = {x=>this.auth=x} required={true}/>
</div>
<div className='form-group'>
<label htmlFor="content" className='control-label'>內容</label>
<textarea id="content" cols="30" rows="10" className='form-control' ref = {x=>this.content=x} required={true}></textarea>
</div>
<div className='form-group'>
<button className='btn btn-info' type='submit'>留言</button>
</div>
</form>
)
}
}
複製程式碼
MessageList.js
import React,{Component} from 'react';
import MessageItem from "./MessageItem";
export default class MessageList extends React.Component {
render(){
return (
<ul className='list-group'>
{/*item裡面有:auth content id createAt*/}
{this.props.messages.map((item,index)=>(
<MessageItem key={index} {...item} del={this.props.delete}/>
))}
</ul>
)
}
}
複製程式碼
MessageItem.js
import React,{Component} from 'react';
export default class MessageItem extends React.Component {
render(){
let {auth,id,createAt,content} = this.props;
return (
<li className='list-group-item'>
留言人:{auth} 內容:{content}
<button className='btn btn-danger btn-xs pull-right' onClick={()=>{this.props.del(id);}}>×</button>
{/*react{}裡不能放物件,要轉成陣列或者字串*/}
<span className='pull-right'>時間:{new Date(createAt).toLocaleString()}</span>
</li>
)
}
}
複製程式碼
整個目錄
9、react環境配置
安裝webpack
yarn init -y
yarn add webpack webpack-dev-server babel-core babel-loader babel-preset-es2015
babel-preset-stage-0 babel-preset-react less-loader less css-loader style-loader
file-loader url-loader html-webpack-plugin --dev
yarn add react react-dom
複製程式碼
檔案配置
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
複製程式碼
webpack.config.js
let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',// 入口
output:{
filename:'build.js',// 打包後的檔名
path:require('path').resolve('./dist')
},
module:{
rules:[
{test:/\.js$/,use:'babel-loader',exclude:/node_modules/},
{test:/\.css$/,use:['style-loader','css-loader']},
{test:/\.less$/,use:['style-loader','css-loader','less-loader']},
{test:/\.(png|jpg|gif)$/,use:'url-loader'}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./index.html'
})
]
}
複製程式碼
babelrc.json
{
"presets":["es2015","stage-0","react"]
}
複製程式碼
package.json
{
"name": "slider",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^1.0.0",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"less": "^3.7.1",
"less-loader": "^4.1.0",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"webpack": "^4.16.0",
"webpack-dev-server": "^3.1.4"
},
"scripts":{
"dev":"webpack-dev-server",
"start":"npm run dev",
"build":"webpack -p"
},
"dependencies": {
"react": "^16.4.1",
"react-dom": "^16.4.1"
}
}
複製程式碼
index.js
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
ReactDOM.render('hello world',window.root);
複製程式碼
環境配置完成後,我們npm run start
10、react輪播圖
目錄
index.js
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Slider from "./Slider";
import './index.less'
// 在渲染slider時要提供一些必備的引數
// delay 2秒動一次
// speed 每個0.5s動一張
// dots小點
// arrows箭頭
// items幾張圖片 webpack裡面圖片不能直接引用字串,不能打包,所以要先引
import a from './1.jpg';
import b from './2.jpg';
import c from './3.jpg';
let items = [{src:a},{src:b},{src:c}];
ReactDOM.render(<Slider
delay={2}
speed={0.5}
autoplay={true}
dots={true}
arrows={true}
items={items}
/>,window.root);
複製程式碼
index.less
*{margin: 0;padding: 0}
ul,li{list-style: none}
.slider-container{
width:400px;
height:300px;
border:5px solid red;
margin:0 auto;
position: relative;
overflow: hidden;
ul{
position: absolute;height: 300px;left: 0;top: 0;
li{
width: 400px;height: 300px;float: left;
img{width: 100%;height: 100%;}
}
}
.slider-arrows{
position: absolute;
width: 100%;
height:40px;
top:50%;
transform: translateY(-50%);
left:0;
span{
width: 30px;height: 40px;line-height: 40px;display: block;
background: #ffffff;text-align: center;-webkit-user-select: none;
cursor: pointer;
&:nth-child(1){
float: left;
}
&:nth-child(2){
float: right;
}
}
}
.slider-dots{
position: absolute;width: 100%;left: 0;bottom: 20px;text-align: center;
span{
background: #ffffff;display: inline-block;width: 20px;height: 20px;
border-radius: 50%;margin: 3px;cursor: pointer;
&.active{
background: rgba(233,222,100,0.8);
}
}
}
}
複製程式碼
Slider.js
import React,{Component} from 'react';
import SliderList from "./SliderList";
import SliderArrows from "./SliderArrows";
import SliderDots from "./SliderDots";
export default class Slider extends React.Component {
constructor(){
super();
this.state = {index:0}// 表示當前是第幾張
}
go =(step)=>{ // 去哪 傳入要動幾個
let index = this.state.index+step;// 先加的
console.log(index);
if(index === this.props.items.length){ // 當等於最後一張時 越界回到0
index = 0;
}
if(index < 0){// 當小於第一張時 回到最後一張
console.log("aa")
index =
this.props.items.length -1;
}
this.setState({
index:index
})
}
turn = () =>{ // 輪播
this.timer = setInterval(()=>{
this.go(1)
},this.props.delay*1000);
}
componentDidMount(){ // 頁面載入完成後 看是否需要自動輪播
if(this.props.autoplay){
this.turn();
}
}
render(){
return (
<div className='slider-container' onMouseEnter={()=>{
clearInterval(this.timer);
}} onMouseLeave={()=>{
this.turn();
}}>
<SliderList index={this.state.index} items={this.props.items} speed={this.props.speed}/>
{this.props.arrows?<SliderArrows go={this.go}/>:null}
{this.props.dots?<SliderDots go={this.go} items={this.props.items} index={this.state.index}/>:null}
</div>
)
}
}
複製程式碼
SliderArrows.js
import React,{Component} from 'react';
export default class SliderArrows extends React.Component {
render(){
return (
<div className='slider-arrows'>
<span onClick={()=>{this.props.go(-1)}}><</span>
<span onClick={()=>{this.props.go(1)}}>></span>
</div>
)
}
}
複製程式碼
SliderDots.js
import React,{Component} from 'react';
export default class SliderDots extends React.Component {
render(){
return (
<div className='slider-dots'>
{this.props.items.map((item,index)=>(
<span key={index} className={this.props.index===index?'active':''}
onClick={()=>{this.props.go(index-this.props.index)}}></span>
))}
</div>
)
}
}
複製程式碼
SliderList.js
import React,{Component} from 'react';
export default class MessageBox extends React.Component {
render(){
let style={
width:this.props.items.length*400+'px', // 設定ul預設寬度
left:this.props.index*400*-1 + 'px', // 根據當前inedex 移動left值
transition:`left ${this.props.speed}s linear`
}
return (
<ul style={style}>
{this.props.items.map((item,index)=>(
<li><img src={item.src} key={index}/></li>
))}
</ul>
)
}
}
複製程式碼
11、react無縫滾動輪播
程式碼在10、react輪播圖基礎上有所變動
SliderList.js
import React,{Component} from 'react';
export default class MessageBox extends React.Component {
render(){
let style={
width:(this.props.items.length+1)*400+'px', // 設定ul預設寬度
left:this.props.index*400*-1 + 'px', // 根據當前inedex 移動left值
transition:`left ${this.props.speed}s linear`
}
return (
<ul style={style} ref='ul'>
{this.props.items.map((item,index)=>(
<li><img src={item.src} key={index}/></li>
))}
{/*實現無縫輪播要再增加一張圖*/}
<li><img src={this.props.items[0].src} alt=""/></li>
</ul>
)
}
}
複製程式碼
Slider.js
import React,{Component} from 'react';
import SliderList from "./SliderList";
import SliderArrows from "./SliderArrows";
import SliderDots from "./SliderDots";
export default class Slider extends React.Component {
constructor(){
super();
this.state = {index:0}// 表示當前是第幾張
}
go =(step)=>{ // 去哪 傳入要動幾個
let index = this.state.index+step;// 先加的
if(index > this.props.items.length){ // 當等於最後一張時 越界回到0
this.$ul.style.transitionDuration = '';// 清除ul上的動畫
this.$ul.style.left = 0;// 回到0處
setTimeout(()=>{// 等動畫移除後並且回到了0點 再增加回動畫時間(dom重新整理一般是30s)
this.$ul.style.transitionDuration = this.props.speed+'s';// 再增加回來這個動畫
index = 1;// 下一次該走1了
this.setState({index});
},30)
return;//因為設定了setTimeout所以要等待setTimeout後再設定最新狀態
}
if(index < 0){// 當小於第一張時 回到最後一張
this.$ul.style.transitionDuration = '';// 清除ul上的動畫
this.$ul.style.left = this.props.items.length*-1*400+'px';
setTimeout(()=>{
this.$ul.style.transitionDuration = this.props.speed+'s';
index = this.props.items.length -1;
this.setState({index});
},30);
return
}
this.setState({
index:index
})
}
turn = () =>{ // 輪播
this.timer = setInterval(()=>{
this.go(1)
},this.props.delay*1000);
}
componentDidMount(){ // 頁面載入完成後 看是否需要自動輪播
if(this.props.autoplay){
this.turn();
}
// 通過ulfs獲取sliderList中的ul元素(操作dom節點)
console.log(this.$ul = this.refs.list.refs.ul)
}
render(){
// 越界了並且只是第一個span加上active屬性
return (
<div className='slider-container' onMouseEnter={()=>{
clearInterval(this.timer);
}} onMouseLeave={()=>{
this.turn();
}}>
<SliderList ref='list' index={this.state.index} items={this.props.items} speed={this.props.speed}/>
{this.props.arrows?<SliderArrows go={this.go}/>:null}
{this.props.dots?<SliderDots go={this.go} items={this.props.items} index={this.state.index}/>:null}
</div>
)
}
}
複製程式碼
SliderDots.js
import React,{Component} from 'react';
export default class SliderDots extends React.Component {
render(){
// 越界了並且只是第一個span加上active屬性
return (
<div className='slider-dots'>
{this.props.items.map((item,index)=>(
<span key={index}
className={(this.props.index===index)||(this.props.index===this.props.items.length&&index===0)?'active':''}
onClick={()=>{this.props.go(index-this.props.index)}}></span>
))}
</div>
)
}
}
複製程式碼
React中Children屬性
this.props.children是獲取元件中間的內容
1、預設不傳遞是undefined
2、傳入一個時是物件型別
3、傳入多個就是陣列型別
4、我們可以用React.Children.map去遍歷this.props.children
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
// this.props.children是獲取元件中間的內容
// 1、預設不傳遞是undefined
// 2、傳入一個時是物件型別
// 3、傳入多個就是陣列型別
// 4、我們可以用React.Children.map去遍歷this.props.children
class Dinner extends Component{
render(){
return <div>
{/*{Object.prototype.toString.call(this.props.children)}*/}
{React.Children.map(this.props.children,(item,index)=>(
<li>{item}</li>
))}
</div>
}
}
ReactDOM.render(<Dinner>
<div>漢堡</div>
<div>漢堡</div>
<div>漢堡</div>
</Dinner>,window.root);
複製程式碼