柚子快報邀請碼778899分享:前端 React 學習筆記
柚子快報邀請碼778899分享:前端 React 學習筆記
React
一、React的簡介
1、介紹
React 是一個用于構建用戶界面的 JAVASCRIPT 庫。 React主要用于構建UI,很多人認為 React 是 MVC 中的 V(視圖)。 React 起源于 Facebook 的內部項目,用來架設 Instagram 的網站,并于 2013 年 5 月開源。 React 擁有較高的性能,代碼邏輯非常簡單,越來越多的人已開始關注和使用它
2、特點
1). 高效 ?React通過對DOM的模擬,最大限度地減少與DOM的交互。
2). 靈活 ?React可以與已知的庫或框架很好地配合。
3). JSX ? JSX 是 JavaScript 語法的擴展。React 開發(fā)不一定使用 JSX ,但我們建議使用它。
4). 組件 ? 通過 React 構建組件,使得代碼更加容易得到復用,能夠很好的應用在大項目的開發(fā)中。
單向響應的數據流 ? React 實現了單向響應的數據流,從而減少了重復代碼,這也是它為什么比傳統(tǒng)數據綁定更簡單。
3、框架對比
與其他框架的共同點是,都采用虛擬dom,和數據驅動
angularJsreactJsvueJs控制器√--過濾器√-√(vue2有,vue3沒有)指令√-√模板語法√-√服務√--組件-√√jsx-√2.0之后加入
二、環(huán)境搭建
1、引入文件的方式(CDN)
1、React.js:
React的核心庫,解析組件,識別jsx
https://cdn.staticfile.org/react/16.4.0/umd/react.development.js
2、reactDom.js:
處理有dom相關的操作
https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js
3、Babel.js
Babel 可以將 ES6 代碼轉為 ES5 代碼,這樣我們就能在目前不支持 ES6 瀏覽器上執(zhí)行 React 代碼。Babel 內嵌了對 JSX 的支持。通過將 Babel 和 babel-sublime 包(package)一同使用可以讓源碼的語法渲染上升到一個全新的水平
https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js
2、官方腳手架(模塊化)
安裝 create-react-app
yarn global add create-react-app | npm install create-react-app -g
創(chuàng)建 react項目
create-react-app 目錄 | npx create-react-app 目錄 | npm init react-app 目錄
yarn eject ? 解構出所有的配置文件 可選
yarn start | ?npm start 開發(fā)
yarn build | ?npm run build 打包
?
//調試 需要安裝給chrome瀏覽器一個插件 react-dev-tools
環(huán)境解析
react: 核心包,解析組件,識別jsx react-dom: 編譯 -> 瀏覽器 react-scrpts: react的項目環(huán)境配置 manifest.json 生成一個網頁的桌面快捷方式時,會以這個文件中的內容作為圖標和文字的顯示內容 registerServiceWorker.js支持離線訪問,所以用起來和原生app的體驗很接近,只有打包生成線上版本的react項目時,registerServiceWorker.js才會有效。服務器必須采用https協(xié)議 對Internet Explorer 9,10和11的支持需要polyfill。
第三方腳手架
yeomen/dva/umi
第一個React程序
? ?
? ?
注意:
1、script標簽的type取值為:text/babel
2、在script標簽里寫的html代碼,不用雙引號或者單引號,這種寫法是下面要講解的jsx。
總結:一個react的程序,就是把JSX通過ReactDOM.render()函數渲染到網頁上。
另外,React的代碼也可以單獨寫在一個文件里,擴展名為js,或者jsx。引入時,記住type=“text/babel”
JSX
1、JSX的介紹
什么是JSX:JSX=javascript xml,就是Javascript和XML結合的一種格式。是 JavaScript 的語法擴展,換句話說:只要你把HTML代碼寫在JS里,那就是JSX。
在實際開發(fā)中,JSX 在產品*打包階段*都已經編譯成純 JavaScript,不會帶來任何副作用,反而會讓代碼更加直觀并易于維護。官方定義是:類 XML 語法的 ECMAScript 擴展。
2、特點:
JSX 執(zhí)行更快,因為它在編譯為 JavaScript 代碼后進行了優(yōu)化。
它是類型安全的,在編譯過程中就能發(fā)現錯誤。
使用 JSX 編寫模板更加簡單快速。
3、JSX的語法
JSX就是把html代碼寫在javascript中,那么,寫在javascript中有啥要求(與原來在html中的區(qū)別),這就是jsx語法要說的內容。
示例:
var msg="哥們!"
?
const element =
Hello, world {msg}
? ? 沒有雙引號,不是字符串?
const List = () => (
?
? ? ? ?
- list item
- list item
- list item
?
? ? ? ? ?
?
? ? ? ? ?
?
? ? ? ? ?
?
? ? ? ?
?
); ?
XML基本語法
只能有一個根標簽,養(yǎng)成外面加上圓括號的習慣。 標簽要閉合(單邊標簽得有斜杠)
元素類型
小寫首字母對應 HTML的標簽,組件名首字母大寫。 注釋使用 / * 內容 */ html標簽內注釋{/* 最外層有花括號*/}
元素屬性
內聯(lián)樣式的style: style屬性不能直接按照html的寫法寫。應該用 JavaScript的寫法(json對象)。 樣式名以駝峰命名法表示, 如font-size需寫成fontSize。默認像素單位是 px(px不用寫) let _style = { backgroundColor:"red" };
ReactDOM.render(
? ?
Hello, world!
,? ?document.getElementById('box')
); 外部樣式的class:HTML中曾經的 class 屬性改為 className(因為class是js的關鍵字),外部樣式時使用 ReactDOM.render(
?
Hello, world!
,? document.getElementById('box')
); for 屬性改為 htmlFor(因為for是js的關鍵字)。(for屬性在html標簽中是擴充單選框|復選框的選擇范圍) 事件名用駝峰命名法。HTML是全小寫(onclick),JSX中是駝峰(onClick)
javascript表達式
使用單花括號。 react里沒有vue中的指令,在JSX的任何地方需要javascript的變量,表達式等等,只需要套上 單花括號就行。 const element =
Hello, {120+130}!
const element =
Hello, {getName("張三瘋")}!
注意:單花括號里只能寫表達式,不能寫語句(如:if,for)
總結:
對比原生,JSX相當于把原生里的html和javascript代碼混寫在一起,
vue中有很多的指令,react中只需要使用單花括號就行。
ReactDOM.render()函數
ReactDOM.render 是 React 的最基本方法。用于將JSX寫的模板(經過模板渲染(把javascript的結果替換單花括號里的內容))轉為 HTML 語言,并渲染到指定的HTML標簽里。
ReactDOM.render( JSX寫的html模板,dom容器對象);
總結:一個react的程序,就是把JSX通過ReactDOM.render()函數渲染到網頁上。
程序員完成的是JSX的編寫。
條件渲染
var sex='女';
if(sex=='男'){
var sexJSX=
我是男的
;}else{
var sexJSX=
我是女的
;}
?
ReactDOM.render(
{sexJSX}
document.getElementById('box')
);
注意:if語句不要寫在單花括號里。
列表渲染
1)、渲染數組:
//數組里存放jsx
var arr=[
? ?
? ?
? ?
]
?
const show = ()=> (
? ?
- {arr}
)
ReactDOM.render(show(),document.getElementById("box"));
2)使用Javascript的map()或者普通for循環(huán)進行列表的渲染
//普通for循環(huán)
?
let arr = ["鉛筆","油筆","鋼筆","毛筆"];
var arr2 =[];
for(let i in arr){
arr2.push(
}
?
const show = ()=> (
- {arr2}
)
?
ReactDOM.render(show(),document.getElementById("box"));
?
//map
const goods = ['鉛筆','鋼筆'];
const goodsJSX = goods.map(function(val,index){
return
});
?
ReactDOM.render(
? ?//以下相當于react里的模板,不能出現js的語句,可以有表達式
{goodsJSX}
document.getElementById('box')
);
組件:定義
react中定義組件有三種寫法:函數方式,ES5的寫法(現在不用了),ES6(類)的寫法
函數方式的組件
函數的返回值是JSX就行。即就是:如果一個函數的返回值是JSX,那么就可以當標簽的方式使用。
// 定義組件,組件名首字母大寫(大駝峰,就是PascalCase)
function MyCom(){
const msg="hello";
return (
- {msg}:三國演義
- {msg}:紅樓夢
)
}
ReactDOM.render(
? ? ? {MyCom()}
? ?
? ?document.getElementById('box')
);
ES5的寫法:
React.CreateClass()函數(React16后,已經被廢棄了)
var MyCom = React.createClass({ ?
?render:function(){ //vue中也有render函數,是完成模板的代碼
? ?return (
? ? ?
? ? ? ?
Hello, world!
? ? ?
? );
}
});
ES6類定義組件:
定義一個類,繼承自 React.Component,并且,在該類里,必須有個render()函數,render函數返回一個JSX代碼。
換句話說:一個普通的ES6的類,繼承自React.Component,有一個render()函數,并且render()函數返回一個JSX,那么就是組件。
// 定義組件
class MyCom extends ?React.Component{
constructor(props){ //props:是外部傳入的數據,相當于vue中的props
super(props);
// state是內部的數據,相當于vue中的date
this.state={
name:"田哥"
}
}
?
render(){
const msg="hello";
return (
- {this.state.name}:三國演義
- {msg}:紅樓夢
)
}
}
多個組件
// 標題
function MyTitle(){
return (
商品列表
)
}
// 詳情
function MyDetail(){
return (
- 鉛筆
- 鋼筆
)
}
ReactDOM.render(
? ?document.getElementById('box')
);
組件:props
props 是組件對外的接口。接收外部傳入的數據。是組件的屬性(等同于html標簽的屬性)。 注意:Props對于使用它的組件內部來說,是只讀的。一旦賦值不能修改。
外部傳值:
<組件名 屬性名1=值1 屬性名2=值2 .. />
屬性值="靜態(tài)值"
屬性值={js數據}
組件內部使用:
1)、函數組件:
{props.屬性名}
示例:
function MyPerson(props){
return (
{props.name}
{props.sex}
)
}
?
ReactDOM.render(
document.getElementById('box')
);
2)、類組件:
{this.props.屬性名}
示例:
class MyPerson extends React.Component{
render(){
return (
{this.props.name}
{this.props.sex}
)
}
}
?
ReactDOM.render(
document.getElementById('box')
);
補充:如果傳遞數據多的話,可以使用對象,但是必須使用擴展運算符(...)
擴展運算符: ES6增加了擴展運算符: ... 三個點是ES幾的_?. 是es幾的-CSDN博客
class MyPerson extends React.Component{
// constructor(props){
// super(props);
// }
render(){
return (
{this.props.name}
{this.props.sex}
)
}
}
?
let person={
name:"張三瘋",
sex:"男"
}
?
ReactDOM.render(
document.getElementById('box')
);
props的默認值
1)、用 ||
function MyPerson(props){
let sex1 = props.sex || "女";
return (
性別:{sex1}
)
}
ReactDOM.render(
document.getElementById('box')
);
2)、defaultProps
格式:
?
//1)、函數式組件和類組件都可以:
?
組件名.defaultProps={
? ?屬性名: 默認值
}
?
//2)、若為類組件,也可以在類的內部使用static修飾(把以下代碼要寫在類里面。
?
static defaultProps={
? ?屬性名: 默認值
}
?
示例:
function MyPerson(props){
let sex1 = props.sex || "女";
return (
姓名:{props.name}
性別:{sex1}
)
}
?
MyPerson.defaultProps={
name:"無名氏"
}
?
ReactDOM.render(
document.getElementById('box')
);
?
props的類型檢查:
注意:react15.5后,React.propTypes已經移入到另外一個庫里,請使用prop-types。
https://cdn.staticfile.org/prop-types/15.6.1/prop-types.js
?
//類型約定:
組件名.propTypes={
? ?屬性名1:PropTypes.類型名,
? ?屬性名2:PropTypes.類型名
? ?//必傳參數
屬性名: PropsTypes.類型名.isRequired
}
?
類型名的可以取值為:
PropTypes.array, PropTypes.bool, PropTypes.func, PropTypes.number, PropTypes.object, PropTypes.string, PropTypes.symbol
如:
function MyPerson(props){
return (
年齡:{props.age}
)
}
?
MyPerson.propTypes={
? ?//注意大小寫
age:PropTypes.number.isRequired
}
?
ReactDOM.render(
document.getElementById('box')
);
類組件:state狀態(tài)機
state 是狀態(tài),狀態(tài)就是數據,state表示組件的內部數據(相當于vue組件中的data)。而props是外部傳入組件的數據,類組件可以直接使用state,但是函數式組件就得用hooks(useState)
定義并初始化
class App extends React.Component {
?constructor(props){ ?
? ? ?super(props); ?
? ? ?//在react中,如果希望狀態(tài)的變化會引起其它數據或者界面的變化,那么,把數據定義在state里。對應著vue中的data。
? ? ?this.state={
? ?//設置狀態(tài)的初始值
? ? ? ?屬性名:屬性值
? ? }
}
}
讀取狀態(tài)
this.state.屬性名
修改狀態(tài)
必須調用setState()函數,不要直接賦值,而且,setState()函數是異步的。
這是因為react框架的響應式原理和vue框架的響應式原理根本就不一樣。
1)、react框架的setState方法內部會調用render函數,這樣就實現了重新渲染
2)、vue框架用的是數據劫持和觀察者模式。
1、修改狀態(tài)時,必須要調用setState。因為,只要調用了setState函數,那就會調用了render()。如果直接賦值,不會把數據響應式地渲染到DOM上(即:沒有調render()函數)
class Book extends React.Component{ ? ? ? ?
?
? ? ? ?constructor(props){
? ? ? ? ? ?super(props);
? ? ? ? ? ?this.state = {
? ? ? ? ? ? ? ?copyright:"版權歸2011"
? ? ? ? ? }
?
? ? ? ? ? ?// 千萬不能這么干,因為,數據修改后,不會響應式的呈現頁面上(不會再次渲染DOM)
? ? ? ? ? ?// this.copyright="版權歸2011";
?
? ? ? ? ? ?// 下面這句話,可以先不管
? ? ? ? ? ?this.changeVal = this.changeVal.bind(this);
? ? ? }
?
? ? ? ?changeVal(){
? ? ? ? ? ?// this.copyright = "哈哈哈";
? ? ? ? ? ?// console.log("this.copyright ",this.copyright);
?
? ? ? ? ? ?// 這不行,這個不會把數據響應式地渲染到DOM上。(即:沒有調render()函數)
? ? ? ? ? ?// this.state.copyright = "哈哈哈";
?
? ? ? ? ? ?// 這 行,因為,只要調用了setState函數,那就會調用了render();
? ? ? ? ? ?this.setState({
? ? ? ? ? ? ? ?copyright:"哈哈哈"
? ? ? ? ? });
? ? ? }
?
? ? ? ?render=()=>{
? ? ? ? ? ?console.log("render");
? ? ? ? ? ?return (
? ? ? ? ? ? ? ?
- 書名:{this.props.name}
- 書齡:{this.props.age}
- {this.state.copyright}
- {this.copyright}
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? )
? ? ? }
? }
?
? ?Book.propTypes = {
? ? ? ?"name":PropTypes.string.isRequired,
? ? ? ?"age":PropTypes.number
? }
?
? ?const jsx =
?
? ?ReactDOM.render(jsx,document.getElementById("box"));
//淺合并state
this.setState({
屬性名:屬性值
})
?
示例代碼:
//1、假設定義的狀態(tài)是:
this.state = {
? ?person:{
? ? ? ?name:"魏鵬",
? ? ? ?sex:"男"
? }
}
//2、如果修改時,使用下面的方式,肯定不行。因為,是淺合并,不會進入到下一級的屬性進行對比
this.setState({
? ?person:{
? ? ? ?name:"鵬鵬"
? }
})
?
vue和react在響應式上的區(qū)別:
1、vue:背后使用了數據劫持和觀察者模式
2、react,修改數據時,調用setState,setState內部調用了render。
2、setState()函數是異步的
//改變狀態(tài)前想做一些事情:
this.setState((prevState,prevProps)=>{
?//一般是用于在setState之前做一些操作,this.state==prevState==修改之前的state
? ?
? return {
? ?sname:value
}
})
?
//改變狀態(tài)后想做一些事情(如:使用新值):
this.setState({
?屬性名:屬性值
}, () => {
? ?
?//一般是用于在setState之后做一些操作。
?//this.state == 修改之后的state
? ?
})
?
?
//改變狀態(tài)前后都想做一些事情:
this.setState((prevState)=>{
? ?// prevState:是舊值
? ?console.log("prevState",prevState)
? ?return {
? ? ? ?age:15
? }
},()=>{
? ?// this.state:就是新值。
? ?console.log(this.state.age);
});
class Book extends React.Component {
?
? ? ? ?constructor() {
? ? ? ? ? ?super();
? ? ? ? ? ?this.msg = "hi";
? ? ? ? ? ?this.state = {
? ? ? ? ? ? ? ?name: "三國演義",
? ? ? ? ? ? ? ?author: "李茂軍02"
? ? ? ? ? }
? ? ? ? ? ?this.changeVal = this.changeVal.bind(this);
? ? ? }
?
? ? ? ?changeVal() {
?
? ? ? ? ? ?// setState函數是異步的
? ? ? ? ? ?// this.setState({
? ? ? ? ? ?// ? ? name:"ddd"
? ? ? ? ? ?// });
//console.log("this.state.name", this.state.name);//三國演義
? ? ? ? ? ?
? ? ? ? ? ?// 在修改狀態(tài)之前做事情
? ? ? ? ? ?// this.setState((prevState)=>{
? ? ? ? ? ?// ? ? console.log("prevState",prevState); //三國演義
? ? ? ? ? ?// ? ? console.log("this.state",this.state);//三國演義
? ? ? ? ? ?// ? ? return {
? ? ? ? ? ?// ? ? ? ? name:"ddd"
? ? ? ? ? ?// ? ? }
? ? ? ? ? ?// });
?
? ? ? ? ? ?// 在修改之后做一些事情
?
? ? ? ? ? ?this.setState({
? ? ? ? ? ? ? ?name: "ddd"
? ? ? ? ? }, () => {
? ? ? ? ? ? ? ?console.log("this.state.name", this.state.name);//ddd
? ? ? ? ? ? ? ?console.log(document.getElementById("pId").innerHTML);//ddd
? ? ? ? ? });
?
? ? ? }
?
? ? ? ?render() {
? ? ? ? ? ?console.log("render");
? ? ? ? ? ?return (
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
{this.msg}
? ? ? ? ? ? ? ? ? ?
書名:{this.state.name}
? ? ? ? ? ? ? ? ? ?
作者:{this.state.author}
? ? ? ? ? ? ? ? ? ?
價格:{this.props.price}
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? )
? ? ? }
? }
?
? ?const h = (
? ? ? ?
hello 我是react
? ? ? ?
? ? ? ? ? ?
? ? ? ?
? ?
?
? ?ReactDOM.render(
? ? ? ?h,
? ? ? ?document.getElementById("box")
? );
?
注意:
1、 直接修改state屬性的值不會重新渲染組件 ,如:this.state.msg="hello"; //親,千萬不要這么干
補充:
給引用類型的某個屬性賦值(淺合并)
class Book extends React.Component{ ? ?
?
? ? ? ?constructor(props){
? ? ? ? ? ?super(props);
? ? ? ? ? ?this.state = {
? ? ? ? ? ? ? ?book:{
? ? ? ? ? ? ? ? ? ?name:"做一個有錢的人",
? ? ? ? ? ? ? ? ? ?age:22
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? ? ?this.changeVal = this.changeVal.bind(this);
? ? ? }
?
? ? ? ?changeVal(){
? ? ? ? ? ?// 這是不行的
? ? ? ? ? ?// this.setState({
? ? ? ? ? ?// ? ? "book.name":"做一個有意義的人"
? ? ? ? ? ?// });
?
? ? ? ? ? ?// 得這樣做(浪費了空間):就得這么寫
? ? ? ? ? ?let b = {
? ? ? ? ? ? ? ?...this.state.book,
? ? ? ? ? ? ? ?name:"做一個有意義的人"
? ? ? ? ? };
? ? ? ? ? ?this.setState({
? ? ? ? ? ? ? ?book:b
? ? ? ? ? });
? ? ? ? ? ?
? ? ? ? ? ?//或者這樣做(節(jié)約了空間):這樣不推薦,因為,這樣破壞了setState的異步
? ? ? ? ? ?let b = this.state.book;
? ? ? ? ? ?b.name = "大學";
? ? ? ? ? ?this.setState({
? ? ? ? ? ? ? ?book:b
? ? ? ? ? });
?
? ? ? }
?
? ? ? ?render=()=>{
? ? ? ? ? ?return (
? ? ? ? ? ? ? ?
- {this.state.book.age}
- {this.state.book.name}
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? )
? ? ? }
? }
示例代碼:
1)、基本示例:
class MyPerson extends React.Component{
constructor(props){
super(props);
this.state={
age:12
}
this.changeAge = this.changeAge.bind(this);
}
?
changeAge(){
this.setState({
age:this.state.age+1
});
}
?
render(){
return (
年齡:{this.state.age}
);
}
}
2)、setState是異步的示例:
?
class MyPerson extends React.Component{
constructor(props){
super(props);
this.state={
age:12,
isAdult:"未成年"
}
this.changeAge = this.changeAge.bind(this);
}
/*
這種寫法達不到效果:
changeAge(){
this.setState({
age:this.state.age+1
});
// setState是異步的,所以,以下的打印不是最新的值
console.log(this.state.age);
// setState是異步的,所以,以下的判斷達不到效果
if(this.state.age>=18){
this.setState({
isAdult:"已成年"
});
}
}
*/
changeAge(){
this.setState({
age:this.state.age+1
},()=>{
console.log(this.state.age);
if(this.state.age>=18){
this.setState({
isAdult:"已成年"
});
}
});
}
?
render(){
return (
年齡:{this.state.age}
年齡:{this.state.isAdult}
);
}
}
無狀態(tài)組件
無狀態(tài)組件就是組件內部沒有(不需要)state,無狀態(tài)組件也可以理解為展示組件,僅做展示用,可以根據外部傳來的props來渲染模板的內容,內部沒有數據。相當于木偶(沒腦子)。你說顯示啥,咱就顯示啥。
var MyFooter = (props) => (
????
);
僅僅只是一個函數,就ok了。
有狀態(tài)組件
有狀態(tài)組件就是不但外部可以傳入,內部也有state。
有狀態(tài)組件也可以理解為容器組件,用來容納展示組件,在容器組件里處理數據的邏輯,把結果在展示組件里呈現。容器組件也叫智能組件
創(chuàng)建有狀態(tài)組件如下:
class Home extends React.Component { //容器組件
? ?constructor(props) {
? ? ? ?super(props);
this.state={
Hots:[],
? ? ? Goodslist:[]
}
}
? ?getHots(){
? ? ? ?發(fā)送axios請求獲取數據
? }
?
? ?getGoodsList(){
? ? ? 發(fā)送axios請求獲取數據
? }
? ?render() {
? ? ? ?return (
? ?
? ?
? ? ? )
? }
}
注意:
做項目時,經常會把有狀態(tài)組件和無狀態(tài)組件進行結合使用。
1)、 有狀態(tài)組件:一般會使用類組件,處理數據的業(yè)務邏輯,包括和后端進行交互,獲取數據。把數據傳給無狀態(tài)組件
2)、 無狀態(tài)組件:一般會使用函數式組件,只做展示用,沒有自己的數據,會接收有狀態(tài)組件傳來的數據。
事件處理
React事件的特點:
1、React 事件綁定屬性的命名采用駝峰式寫法,而不是小寫。如:onClick。
2、如果采用 JSX 的語法你需要傳入一個函數作為事件處理函數,而不是一個字符串(DOM 元素的寫法)
3、阻止事件的默認行為,不能使用return false, 必須使用 preventDefault。
4、事件處理函數里的this是undefined(啊………),如果希望事件處理函數里的this是React組件對象本身,則需要用bind。
事件語法
1、格式
示例:
class Home extends React.Component{
? ?
fn01() {
?console.log("fn01");
}
? ?
render(){
?return (
?
? ?
? ? {console.log("事件綁定箭頭函數")}} />
? ?
)
}
}
2、事件處理函數里的this
事件處理函數里的this是undefined,如何讓事件處理函數里的this是React組件對象本身
一、使用bind
1)、構造器(構造函數)里:this.方法=this.方法.bind(this) ?
2)、在事件綁定時寫,onClick={this.方法.bind(this)}
?
二、使用箭頭函數
3)、定義方法時,直接使用箭頭函數: 方法=()=>{箭頭函數定義方法} ?
4)、事件綁定時,使用箭頭函數:onClick={()=>this.方法()}
如:
class MyPerson extends React.Component{
constructor(props){
super(props);
this.state={
age:12,
isAdult:"未成年"
}
this.changeAge = this.changeAge.bind(this);
}
?
changeAge(){
………………………………
}
//直接使用箭頭函數
changeAge=()=>{
this指向會找上一級
}
?
render(){
return (
{this.changeAge()}} />
年齡:{this.state.age}
年齡:{this.state.isAdult}
);
}
}
3、事件對象
event對象是經過react處理過的。
如何獲取事件對象------直接聲明即可。
實例方法(ev) ev 代理事件對象 ,ev.target 返回真實DOM
事件對象的獲?。?/p>
1)、直接聲明(沒有其它參數的情況下)
changeAge1=(ev)=>{
? ?console.log(ev);
? ?console.log(ev.target);
}
?
this.changeAge1(ev)} />
2)、箭頭函數里直接聲明(有其它參數的情況下)
changeAge2(ev,num){
? ?console.log(ev);
? ?console.log(ev.target);
? ?console.log(num);
}
//注意:給onClick綁定的函數,還是只有一個參數,這個參數就是事件對象,此處在綁定的函數里再調用另外一個函數進行傳參
this.changeAge2(ev,2)} />
注意:給事件屬性綁定的函數,永遠只會有一個參數,該參數就是事件對象。
4、阻止瀏覽器的默認行為:
只能用preventDefault,不能在事件函數里使用return false
組件的內容 :children
組件的內容,使用 props.children屬性獲取
?
function Button(props) {
?console.log("props",props);
?return (
? ?<>
? ? ?
? ?>
)
}
?
refs
獲取DOM的。
表示對組件真正實例(也就是html標簽,也就是DOM對象)的引用,其實就是ReactDOM.render()返回的組件實例;ref可以寫在html官方標簽里,也可以寫在組件(自定義標簽里),和vue的ref是同樣的意思。
官方建議: 勿過度使用 Refs(盡量不要操作DOM),在對邏輯進行處理的時候盡量優(yōu)先考慮state(數據驅動)
用法
1). 賦值為 字符串(官方不推薦使用)
?
? this.refs.username.value
2). 賦值為 回調函數
當給 HTML 元素添加 ref 屬性時,ref 回調接收了底層的 DOM 元素作為參數。
ref 回調會在componentDidMount 或 componentDidUpdate 這些生命周期回調之前執(zhí)行。
//ref的值賦成回調函數時,回調的參數就是當前dom元素。
// callback refs 回調
?
? ?
? ?
this.定義一個實例屬性 //后期用作訪問jsx元素
//當組件掛載時,將 DOM el元素傳遞給 ref 的回調
//當組件卸載時,則會傳遞 null。
//ref 回調會在 componentDidMount 和 componentDidUpdate 生命周期之前調用
? ?
? ?如:
? this.input1 ?= currDom} />
? this.input2 = currDom} />
3). React.createRef() (React16.3提供)
使用此方法來創(chuàng)建ref。將其賦值給一個變量,通過ref掛載在dom節(jié)點或組件上,該ref的current屬性將能拿到dom節(jié)點或組件的實例
//1、在構造函數里
// 實例化了ref對象
this.firstRef = React.createRef() //發(fā)生在構造器
?
//2、掛載在ref屬性上
? ?
//3、獲取dom元素
this.firstRef.current //current是官方的屬性
受控元素(組件)
一個標簽(組件)受react中控制,受數據,受函數,等等(其實,就是一個標簽(組件)里用了react里的東西)。
表單的value受控,受數據控制,
//model 控制 view。
? //view 控制 model
雙向綁定
class MyCom extends React.Component{
constructor(){
super();
this.state={
username:"李祥"
}
}
?
changeVal(e){
this.setState({
username:e.target.value
})
}
?
render(){
return (
this.changeVal(e)} />
)
}
}
處理多個輸入元素(雙向綁定的封裝)
可以為每個元素添加一個 name 屬性(通常和數據名一致),處理函數根據 event.target.name 的值來選擇要做什么
class MyCom extends React.Component{
? ?constructor(props){
? ? ? ?super(props);
? ? ? ?this.state={
? ? ? ? ? ?userName:'',
? ? ? ? ? ?content:''
? ? ? }
? }
?
? ?changeVal(ev){
? ? ? ?this.setState({
? ? ? ? ? [ev.target.name]:ev.target.value
? ? ? });
? }
?
? ?render(){
? ? ? ?return (
? ? ? ? ? ?
? ? ? ? ? ? ? ?
{this.state.userName}
? ? ? ? ? ? ? ?this.changeVal(ev)} />
? ? ? ? ? ? ? ?
{this.state.content}
? ? ? ? ? ? ? ?this.changeVal(ev)} />
? ? ? ? ? ?
? ? ? )
? }
}
非受控元素(組件)
要編寫一個非受控組件,而不是為每個狀態(tài)更新都編寫數據處理函數,你可以 使用 ref 來從 DOM 節(jié)點中獲取表單數據
默認值
表單元素上的 value 將會覆蓋 DOM 節(jié)點中的值,在非受控組件中,你經常希望 React 能賦予組件一個初始值,但是不去控制后續(xù)的更新,指定一個 defaultValue 屬性,而不是 value
可選案例:
增刪改查
生命周期及其鉤子函數
react組件生命周期經歷的階段:初始化階段 -----> 運行階段(更新期)-----> 銷毀階段
初始化階段 (掛載):(在這個階段完成了vue中數據掛載和模板渲染)
組件實例被創(chuàng)建并插入 DOM 中時,其生命周期鉤子函數的調用順序如下(粗體為使用比較多的):
1)、constructor
構造函數里,可以做狀態(tài)的初始化,接收props的傳值
2)、componentWillMount: 在渲染前調用,相當于vue中的beforeMount
3)、render
渲染函數,不要在這里修改數據。 vue中也有render函數。
4)、componentDidMount 相當于vue中的 mounted
渲染完畢,在第一次渲染后調用。之后組件已經生成了對應的DOM結構, 如果你想和其他JavaScript框架(swiper)一起使用,可以在這個方法中使用,包括調用setTimeout, setInterval或者發(fā)送AJAX請求等操作,相當于vue的mounted
運行中階段(更新)(相當于vue中更新階段)
當組件的 props 或 state 發(fā)生變化時會觸發(fā)更新(嚴謹的說,是只要調用了setState()或者改變了props時)。組件更新的鉤子函數調用順序如下:
1)、shouldComponentUpdate(nextProps, nextState) 組件應該更新嗎? 需要返回true或者false。如果是false,那么組件就不會繼續(xù)更新了。
2)、componentWillUpdate,即將更新。相當于vue中的 beforeUpdate
3)、 componentWillReceiveProps(nextProps): 在組件接收到一個新的 prop (更新后)時被調用。這個方法在初始化render時不會被調用。nextProps 是props的新值,而 this.props是舊值。相當于vue中的 beforeUpdate
4)、render
不要在這里修改數據
5)、componentDidUpdate。相當于vue中的 updated
在組件完成更新后立即調用。在初始化時不會被調用。 相當于vue中的updated
銷毀階段(卸載)
componentWillUnmount()
即將卸載,可以做一些組件相關的清理工作,例如取消計時器、網絡請求等
示例:
?
class MyPerson extends React.Component{
constructor(props){
super(props);
console.log("====constructor===");
this.state={
age:12
}
}
?
changeAge2(ev,num){
this.setState({
age:this.state.age+1
});
}
?
componentWillMount(){
console.log("====首次渲染前:componentWillMount===");
}
?
componentDidMount(){
console.log("====首次渲染完畢:componentDidMount===");
}
?
shouldComponentUpdate(){
console.log("====希望更新組件嗎?(state發(fā)生變化了):shouldComponentUpdate===");
return true;
}
?
componentWillUpdate(){
console.log("====組件更新前(state發(fā)生變化了):componentWillUpdate===");
}
?
componentWillReceiveProps(){
console.log("====組件更新前(props發(fā)生變化了):componentWillReceiveProps===");
}
?
componentDidUpdate(){
console.log("====組件更新后:componentDidUpdate===");
}
?
render(){
console.log("====render===");
return (
this.changeAge2(e,2)} />
年齡:{this.state.age}
);
}
}
?
ReactDOM.render(
document.getElementById('box')
);
注意(面試題):
父更新則子更新,子更新父不更新
?
class BookList extends React.Component{ ? ?
? ?constructor(props){
? ? ? super();
? ? ? console.log("BookList:constructor");
? }
? ?// 首次渲染前
? ?componentWillMount(){
? ? ? ?console.log("BookList:componentWillMount");
? } ? ?
? ?// 首次渲染后
? ?componentDidMount(){
? ? ? ?console.log("BookList:componentDidMount");
? }
?
? ?change=()=>{
? ? ? ?this.setState({});
? }
?
? ?componentWillUpdate(){
? ? ? ?console.log("BookList:componentWillUpdate");
? }
?
? ?componentDidUpdate(){
? ? ? ?console.log("BookList:componentDidUpdate");
? }
?
? ?render(){
? ? ? ?console.log("BookList:render"); ? ? ? ?
? ? ? ?return (
? ? ? ? ? ?
? ? ? ? ? ? ? ?
書籍列表
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ) ? ? ? ?
? }
}
?
class Book extends React.Component{ ? ?
? ?constructor(props){
? ? ? ?super();
? ? ? console.log("Book:constructor"); ? ? ?
? }
?
? ? ? ?// 首次渲染前
? ?componentWillMount(){
? ? ? ?console.log("Book:componentWillMount");
? }
? ?
? ?// 首次渲染后
? ?componentDidMount(){
? ? ? ?console.log("Book:componentDidMount");
? }
?
? ?componentWillUpdate(){
? ? ? ?console.log("Book:componentWillUpdate");
? }
?
? ?componentDidUpdate(){
? ? ? ?console.log("Book:componentDidUpdate");
? }
?
? ?change=()=>{
? ? ? ?this.setState({});
? }
?
? ?render(){
? ? ? ?console.log("Book:render"); ? ?
? ? ? ?return (
? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ) ? ? ? ?
? }
}
?
ReactDOM.render(
? ?
? ? ? ?
? ? ? ?
? ?
? ?document.getElementById("box")
);
react17,不建議使用的鉤子函數(面試過程中有問到):
UNSAFE_componentWillMount 不建議用,可以會出現bug,不能初始化因為會受到React16.xFiber的協(xié)調算法,函數會執(zhí)行多次,如果把異步請求放到該鉤子函數中,異步請求可能也會執(zhí)行多次。
UNSAFE_componentWillReceiveProps UNSAFE_componentWillUpdate
es5版(可以不用看了)
實例化
取得默認屬性(getDefaultProps) 外部傳入的props 初始狀態(tài)(getInitialState) state狀態(tài) 即將掛載 componentWillMount 描畫VDOM render 掛載完畢 componentDidMount
補充:vue組件更新和react組件更新的觸發(fā)條件(更新時機)不一樣
1、vue的更新觸發(fā)條件:
? ?
{{msg}}
? ?
?
data(){
? ?return {
? ? ? ?msg:'hi',
? ? ? ?str:"hello"
? }
}
?
methods:{
? ?changeMsg(){
? ? ? ?this.str="hhhhh";//不會引起組件的重新渲染
? ? ? ?this.msg="hi";//不會引起組件的重新渲染
? ? ? ?this.msg="hello"http://引起組件的重新渲染
? }
}
vue組件更新_引起組件更新的起因,什么會引發(fā)組件的更新_別人的vue一運行components內容就改變-CSDN博客
2、react組件的更新觸發(fā)條件:
只要改變props或者state,無論數據有沒有顯示在模板里,都會引發(fā)渲染。并且,只要是賦值,無論賦值前后的數據是不是一樣都會引起組件的渲染(其實就是只要調用setState函數,傳參為空對象就行),所以,在React里需要牽扯到組件更新的性能優(yōu)化。
性能優(yōu)化
react組件更新的時機:只要setState()被調用了,就會引起組件更新。不論數據改前改后是否一樣,或者修改的數據是否在頁面上呈現,都會進行更新組件。
但是vue中,數據必須在模板使用,并且數據發(fā)生變化才能引起組件的重新渲染。所以,在React里,如果要做性能優(yōu)化,可以把這種無效的渲染阻止掉。
1)、shouldComponentUpdate()鉤子函數
?
shouldComponentUpdate(nextProps, nextState){
console.log("this.state",this.state);//當前狀態(tài)
console.log("nextState",nextState);//新的狀態(tài)
? ?return false;//不再渲染。
}
?
我們可以在這個鉤子函數里return false,來跳過組件更新
示例:
class Books extends React.Component {
?
? ? ? ?constructor(props) {
? ? ? ? ? ?super();
? ? ? ? ? ?this.state = {
? ? ? ? ? ? ? msg:"hello react",
? ? ? ? ? ? ? ?d_str:"how are you!" ? //約定:以 d_ 開頭的數據不再模板上渲染
? ? ? ? ? }
? ? ? }
?
? ? ? ? ?// 比較兩個對象的內容(屬性不是以d_開頭)是否一樣。
? ? ? ?compareObj(obj1,obj2){
? ? ? ? ? ?// 1、兩個對象的屬性數量如果不一樣,那么就肯定不相等
? ? ? ? ? ?if(Object.keys(obj1).length!=Object.keys(obj2).length){
? ? ? ? ? ? ? ?return false;
? ? ? ? ? }
? ? ? ? ? ?// 2、兩個對象的屬性數量一樣
? ? ? ? ? ?for(let key in obj1){
? ? ? ? ? ? ? ?if(typeof obj1[key]=="object"){
? ? ? ? ? ? ? ? ? ?if(typeof obj2[key]=="object"){
? ? ? ? ? ? ? ? ? ? ? ?// 兩個屬性都是對象。
? ? ? ? ? ? ? ? ? ? ? ?if(!key.startsWith("d_") && !this.compareObj(obj1[key],obj2[key])){
? ? ? ? ? ? ? ? ? ? ? ? ? ?return false;
? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ?// obj1是對象,obj2不是對象
? ? ? ? ? ? ? ? ? ? ? ?return false;
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? if(typeof obj2[key]=="object"){ ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ?// obj1不是對象,obj2是對象
? ? ? ? ? ? ? ? ? ? ? ?return false;
? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? if(!key.startsWith("d_") && obj1[key]!=obj2[key]){
? ? ? ? ? ? ? ? ? ? ? ? ? ?return false;
? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? ? ?return true;
? ? ? }
?
? ? ? ?shouldComponentUpdate(nextProps,nextState){
? ? ? ? ? ?console.log("shouldComponentUpdate:是否更新?");
? ? ? ? ? ?console.log("this.state",this.state);
? ? ? ? ? ?console.log("nextState",nextState);
? ? ? ? ? ?// if(數據沒有變化 || 數據發(fā)生了變化,但是該數據并不在模板上使用){
? ? ? ? ? ?// ? ? return false
? ? ? ? ? ?// }
?
? ? ? ? ? ?if(this.compareObj(this.state,nextState)){
? ? ? ? ? ? ? ?return false;
? ? ? ? ? }
?
? ? ? ? ? ?return true;
? ? ? }
?
? ? ? changeMsg=()=>{
? ? ? ? ? ?this.setState({
? ? ? ? ? ? ? ?msg:this.state.msg+"1"
? ? ? ? ? ? ? ?// msg:this.state.msg
? ? ? ? ? })
? ? ? }
?
? ? ? ?changeStr=()=>{
? ? ? ? ? ?this.setState({
? ? ? ? ? ? ? ?d_str:this.state.d_str+"1"
? ? ? ? ? })
? ? ? ? ? ?// this.setState({});
? ? ? }
?
? ? ? ?render(){
? ? ? ? ? ?console.log("render"); ? ? ? ? ? ?
? ? ? ? ? ?return (
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
生命周期及其鉤子函數
? ? ? ? ? ? ? ? ? ?
{this.state.msg}
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? )
? ? ? }
? }
?
2)、PureComponent
React15.3中新加了一個 PureComponent 類,只要把繼承類從 Component 換成 PureComponent 即可
PureComponent 默認提供了一個具有淺比較的shouldComponentUpdate方法。只是比較數據(第一層)是否有變化,也不會判斷數據是否在頁面上展示。 當props或者state改變時,PureComponent將對props和state進行淺比較。即:如果說狀態(tài)是引用類型,只要地址發(fā)生變化,照樣會重新渲染。 不能再重寫shouldComponentUpdate
Component和PureComponent的區(qū)別:
當組件繼承自 Component 時,只要setState()被調用了,就會引起組件更新。不論數據改前改后的是否一樣,或者修改的數據是否在頁面上呈現,都會進行更新組件。
PureComponent只能保證數據不變化的情況下不做再次的渲染,而不關心數據是否在頁面上渲染,而且比較數據變化時,只比較第一層。
那么,親,您注意:
1、您為什么要把不在界面上顯示的數據寫在state里呢?難道,直接定義成類的屬性不香嗎?
2、因為,使用setState時,只能改變根屬性,所以,PureComponent只比較一層,確實夠用了。
假如:狀態(tài)是引用類型,那么,修改引用類型的某個屬性時,引用類型的地址肯定會變化。
class MyCom extends React.PureComponent{
constructor(){
super();
this.state={
username:"張三瘋",
age:12,
wife:{
name:"寶寶的寶寶"
}
}
}
?
componentWillUpdate(){
console.log("componentWillUpdate");
}
componentDidUpdate(){
console.log("componentDidUpdate");
}
?
fn(){
//1、 不會引起組件的更新,因為,值沒有變
// this.setState({
// username:this.state.username
// });
//2、 會引起組件的更新,因為,值變了
// this.setState({
// username:"dddd"
// });
//3、 會引起組件的更新,因為,值變了(即使age么有在頁面上顯示,也會引起更新)
// this.setState({
// age:this.state.age+1
// })
?
//4、會引起組件的更新,雖然,name的值沒有變化,但是,wife的值變化了(wife是引用類型)
let obj = this.state.wife;
obj.name="寶寶的寶寶";
this.setState({
wife:obj
});
}
?
render(){
console.log("render");
return (
this.fn()} />
)
}
}
?
腳手架
facebook的官方腳手架
搭建項目:
一、第一種:
create-react-app5.0.3 之前的版本:
1)、安裝 create-react-app (CRA)
npm install create-react-app -g | yarn global add create-react-app
2)、用腳手架創(chuàng)建 react項目
create-react-app? 項目名稱
如:create-react-app reactapp01
注意:項目名稱不能有大寫字母。
3)、 啟動項目:
npm start ? | ? yarn start
二、第二種:
從creact-react-app的5.0.3開始,不再支持全局安裝create-react-app進行搭建項目,而是,直接搭建項目。
1)、搭建項目:
npm init react-app ?項目名稱
2)、 啟動項目:
npm start ? | ? yarn start
目錄解析:
4.1)第一級目錄
node_modules:是項目依賴的模塊
src:是程序員寫代碼的地方,src是source的縮寫,表示源代碼
public: 靜態(tài)資源。react腳手架中的靜態(tài)圖片資源和動態(tài)圖片資源都放在此處
4.2)展開目錄:
Public:
index.html:是html網頁,是個容器。這個文件千萬不敢刪除,也不能改名。
只有Public目錄下 的文件才會被index.html文件引用,這是靜態(tài)資源,index.html不會引用src目錄下的文件
manifest.json: 生成一個網頁的桌面快捷方式時,會以這個文件中的內容作為圖標和文字的顯示內容
src:
src目錄是源代碼,webpack只會打包這個目錄下的文件,所以,把需要打包的文件都放在這個目錄下。
Index.js:是主js文件,千萬不敢刪除,也不能改名
Index.css:是index.js引入的css文件(也是模塊,webpack會把css也打包成模塊) 千萬不敢刪除,也不能改名
App.js:是一個組件示例(模塊),在 index.js里會引入這個組件。我們自己需要寫組件時,只需要復制App.js文件即可。
App.css:是App.js文件引入的css文件(也是模塊,webpack會打包)。
Logo.svg:是圖片
registerServiceWorker.js:支持離線訪問,所以用起來和原生app的體驗很接近,只有打包生成線上版本的react項目時,registerServiceWorker.js才會有效。服務器必須采用https協(xié)議
5)、打包
npm run ?build | yarn build
6)、如果要解構出配置文件:
npm ?run ?eject ?| ?yarn eject ? 解構出所有的配置文件 可選
7)、如果需要調試,安裝react-dev-tools工具
先進入到https://github.com/facebook/react網址 通過git clone https://github.com/facebook/react-devtools.git下載到本地(或者直接點擊下載) 下載之后進入到react-devtools目錄下,用npm安裝依賴
npm --registry https://registry.npm.taobao.org install
然后在npm run build:extension:chrome
環(huán)境配置
1、把配置解構
npm run eject | yarn eject
?
?
2、修改端口
//修改script/start.js
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3001;
?
3、去除eslint 警告
//config/webpack.config.js
//注釋關于eslint的導入和rules規(guī)則
資源限制
本地資源導入(import) 不可以導入src之外的包 圖片聲音等靜態(tài)資源的路徑: 都放在public下,寫路徑時,不要寫public(和vue腳手架不一樣)。因為,react腳手架里不會對靜態(tài)的src路徑對應的圖片打包。
在腳手架里做項目的步驟:
1)、創(chuàng)建目錄
在src目錄下創(chuàng)建以下文件夾:
assets :靜態(tài)資源文件夾 放置的是import或者require引入的資源。 components:組件文件夾 /components/a組件/ a.js 和 a.css pages:頁面文件夾
2)、圖片文件夾
1.如果不希望圖片被打包處理:
把圖片放到public文件夾中,使用絕對路徑(img src="/img/image.jpg" />)
2.如果希望圖片被(webpack)打包
圖片放在assets下。
使用require引用,require(‘圖片的相對路徑'),Require中只能使用字符串不能使用變量。如:
vue腳手架和react腳手架不同
1、編譯打包時,
圖片路徑問題:vue:靜態(tài)路徑會打包,動態(tài)不會;react都不會打包,如果希望react里的靜態(tài)路徑打包,那就使用用require引入圖片。
反向代理:
Proxying API Requests in Development | Create React App
1、安裝模塊(http-proxy-middleware): 這個模塊在vue腳手架里是默認安裝的。
npm install http-proxy-middleware --save-dev
?
yarn add http-proxy-middleware -D
2、在項目源代碼的根目錄創(chuàng)建文件: src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
?
module.exports = function(app) {
?console.log("proxy");
?app.use(
? ?'/api',
? ?createProxyMiddleware({
? ? ?target: 'http://xmb8nf.natappfree.cc',
? ? ?changeOrigin: true,
? ? ?// 重寫接口路由
? ? ?pathRewrite: {
? ? ? ?'^/api': ''
? ? }
? })
);
};
3、重啟服務器
yarn start
第三方腳手架
yeomen/dva/umi
組件傳值
1).父子組件通信方式
(1) Props傳遞數據與Props傳遞方法
父組件--->子組件:用props傳遞數據
1)、定義組件時,聲明props形參:
class Person extends React.Component {
?constructor(props) {
? ? super(props);
}
render() {
? ?return (
? ? ? ?
? ? ? ? 姓名:{this.props.name}
? ? ?
? );
}
}
?
2)、使用組件時,傳入參數:
子組件--->父組件,用props傳遞方法
父組件利用props傳遞方法給子組件,子組件回調這個方法的同時,將數據傳遞進去,使得父組件的相關方法得到回調,這個時候就可以把數據從子組件傳遞給父組件了
//1、父組件
class App extends React.Component {
? ?constructor(){
? ? ? ?super();
? ? ? ?this.state={
? ? ? ? ? ?t:"Home"
? ? ? }
? }
?
? ?changeData(str){
? ? ? ?console.log("子傳給了父親:",str);
? }
?
? ?render = () => (
? ? ? ?
? ? ? ? ?
? ? ? ? ?
? ? ? ?
? )
};
?
//2、子組件
class Home extends React.Component {
? ?constructor(name){
? ? ? ?super();
? ? ? ?this.name=name;
? ? ? ?this.state={
? ? ? ? ? ?msg:"hello "
? ? ? }
? }
?
? ?render = () => (
? ? ? ?
? ? ? ? ? ?
我是{this.props.title}
? ? ? ? ? ?
{this.state.msg+this.props.title}
? ? ? ? ? ?
? ? ? ? ? ? ? ?type="button"
? ? ? ? ? ? ? ?value="傳給父親"
? ? ? ? ? ? ? ?onClick={()=>this.props.fn('HELLO dad')} />
? ? ? ?
? )
}
(2) ref 標記
組件間通信除了props外還有onRef方法,不過React官方文檔建議不要過度依賴ref。
思路:當在子組件中調用onRef函數時,正是調用從父組件傳遞的函數。this.props.onRef(this)這里的參數指向子組件本身,父組件接收該引用作為第一個參數:onRef = {ref =>(this.child = ref)}然后它使用this.child保存引用。之后,可以在父組件內訪問整個子組件實例,并且可以調用子組件函數。
//子組件
class Person extends React.Component {
?constructor(props) {
? ?super(props);
? ?this.state={
? ? ? ?msg:"hello"
? }
}
?//渲染完畢
?componentDidMount(){
? ? ?//子組件首次渲染完畢后,把子組件傳給父組件(通過props的onRef)
? ? ?this.props.onRef(this);//把子組件傳給父組件,注意,此處的this是子組件對象
}
? ? ?
?render() {
? ?return ( ?
}
}
?
//父組件:
class Parent extends React.Component{
? ?constructor(props){
? ? ? ?super(props);
? ? ? ?this.child =null;//定義了一個屬性,該屬性表示子組件對象
? }
? ?
? ?testRef=(ref)=>{
? ? ? ?this.child = ref //給父組件增加屬性child,child保存著子組件對象
? ? ? ?console.log(ref) // -> 獲取整個Child元素
? }
? ?
? ?handleClick=()=>{
? ? ? ?alert(this.child.state.msg) // -> 通過this.child可以拿到child所有狀態(tài)和方法
? ? ? ?this.child.setState({
? ? ? ? ?msg:"哈哈"
? ? ? })
? }
? ?
? ?render(){
? ? ? ?return (
? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? ?
? ? ? )
? }
}
ReactDOM.render(
2).非父子組件通信方式
(1)訂閱發(fā)布(pubsub模塊)
相當于vue的事件總線
訂閱: token=pubsub.subscribe('消息名',回調函數('消息名',數據){ 函數體 }),相當于事件總線的$on綁定事件 發(fā)布: pubsub.publish('消息名',數據),相當于事件總線的$emit觸發(fā)事件 清除指定訂閱:pubsub.unsubscribe(token|'消息名'); 清除所有:pubsub.unsubscribeAll()
var pubsub = new PubSub();
?
class MyCom1 extends React.Component{
constructor(){
super();
}
testf(){
pubsub.publish('user_add', {
firstName: 'John',
lastName: 'Doe',
email: 'johndoe@gmail.com'
});
}
render(){
return (
MyCom1
this.testf()} />
)
}
}
?
class MyCom2 extends React.Component{
constructor(){
super();
this.state={
msg:"hi"
}
//訂閱
pubsub.subscribe('user_add', function (data) {
console.log('User added');
console.log('user data:', data);
});
}
?
render(){
return (
MyCom2
)
}
}
?
const jsx =
?
ReactDOM.render(
jsx,
? ?document.getElementById('box')
);
(2)狀態(tài)提升
使用 react 經常會遇到幾個組件需要共用狀態(tài)(數據)的情況。這種情況下,我們最好將這部分共享的狀態(tài)提升至他們最近的父組件當中進行管理。 即:把原本屬于子組件的state,放在父組件里進行管理。
https://www.reactjscn.com/docs/lifting-state-up.html
父組件:src/components/Parent.js
?
import React from "react";
import Son1 from "./Son1";
import Son2 from "./Son2";
?
export default class Parent extends React.Component {
? ?constructor(props){
? ? ? ?super(props);
? ? ? ?this.state = {val:'默認值'};
? }
?
? ?tempFn(val){
? ? ? ?console.log("tempFn");
? ? ? ?console.log("val",val);
? ? ? ?
? ? ? ?this.setState({
? ? ? ? ? ?val:val
? ? ? })
? ? ? ?
? }
? ?
? ?render = () => (
? ? ? ?
? ? ? ? ? ? 父組件
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ?
? )
}
?
Son1組件
src/components/Son1.js
?
export default class Son1 extends React.Component {
? ?constructor(props){
? ? ? ?super(props);
? ? ? ?this.state = {
? ? ? ? ? ?name:"我是son1"
? ? ? };
? }
?
? ?chuan(){
? ? ? ?this.props.onMyClick(this.state.name);
? }
? ?
? ?render = () => (
? ? ? ?
? ? ? ? ? ? son1組件
? ? ? ? ? ? {this.chuan()}} />
? ? ? ? ? ?
? ? ? ?
? )
}
?
Son2組件
src/components/Son2.js
?
export default class Son2 extends React.Component {
? ?constructor(props){
? ? ? ?super(props);
? ? ? ?this.state = {};
? }
? ?
? ?render = () => (
? ? ? ?
? ? ? ? ? ?
son2組件
? ? ? ? ? ?
val:{this.props.val}
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ?
? )
}
3).context 狀態(tài)樹(共享的狀態(tài)樹)傳參
在平時使用react的過程中,數據都是自頂而下的傳遞方式,例如,如果在頂層組件(如:App)的state存儲了theme主題相關的數據作為整個App的主題管理。那么在不借助任何第三方的狀態(tài)管理框架的情況下,想要在子組件里獲取theme數據,就必須的一層層傳遞下去,即使兩者之間的組件根本不需要該數據,這樣的傳遞方式很麻煩(代碼復雜),并且浪費內存。
Context 旨在共享一個組件樹,可被視為 “全局” 的數據,達到越級傳遞,場景:當前經過身份驗證的用戶,主題或首選語言,包括管理當前的 locale,theme,或者一些緩存數據。
createContext():用于創(chuàng)建context對象(上下文),需要一個defaultValue的參數,并返回一個包含Provider(提供者),以及Consumer(消費者)的對象
Provider:提供者,提供數據。接收一個將要被往下層層傳遞的props,該值需在組件樹最頂層設置。一個Provider可以關聯(lián)到多個Consumers。這是一個頂層用于提供context的組件,包含一個value的props,value是實際的context數據。
Consumer:消費者,使用者,接收一個函數作為子(DOM)節(jié)點,函數接收當前 context 的值。這是一個底層用于獲取context的組件,需要一個函數作為其子元素,該函數包含一個value的參數,這個參數就是上層所傳遞context value
創(chuàng)建一個context對象,并設定默認值。語法如下:
const {Provider, Consumer} = React.createContext(defaultValue);
從父朝子傳值(外朝內)
示例代碼:
context模塊: ./src/utils/myContext;
?
import {createContext} from "react";
?
export const {Provider, Consumer} = createContext({
?name:"張三瘋"
});
?
?
//頂層組件:./src/App.js
?
import {Provider} from "./utils/myContext"
?
function App() {
?let val={
? ?name:"hi"
};
?return (
? ?
? ? ?
? ? ? ?
? ? ?
? ?
);
}
?
//孫子組件: App->Home->Goodslist
import {Consumer} from "../utils/myContext";
?
export default class GoodsList extends React.Component {
?
? ?render = () => (
? ? ? ?
? ? ? ? ? ?
商品列表:
? ? ? ? ? ?? ? ? ? ? ?
? ? ? ? ? ? ? ? ? {
(value) ?=>
}
? ? ? ? ? ?
? ? ? ?
? )
}
注意:
export const {Provider, Consumer} = createContext({
name:"張三瘋" });
這個默認值是在頂層組件沒有使用Provider組件時的值,而不是,沒有給value屬性賦值時的值。
即:頂層組件的代碼如下:
function App() {
let val={
? ? name:"hi"
};
return (
?
? ? ? ?
?
);
}
在子組件改變狀態(tài)樹的數據
//根組件:
import {Provider} from "./utils/myContext"
?
export default class App extends React.Component {
?constructor(props){
? ?super(props); ?
? ?this.state={
? ? ? ?name:"宋晨",
? ? ? ?setName:this.fn
? }
}
?
?fn=(str)=>{
? ? ?this.setState({
? ? ? ?name:str
? ? });
}
?
?render = () => (
? ?
? ? ?
? ? ? ?
? ? ?
? ?
)
}
?
//子組件里:
import {Consumer} from "../../utils/myContext";
?
export default class Banner extends Component {
?
render = () => (
?
? ?
? ? ? {
? ? ? ? ? (obj)=>(
? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ?
姓名:{obj.name}
? ? ? ? ? ? ? ? ?obj.setName("hiwww")} />
? ? ? ? ? ? ?
? ? ? ? )
? ? ? }
? ? ? ?
? ?
? )
}
高階組件(HOC)的構建與應用
https://www.reactjscn.com/docs/higher-order-components.html
高階組件(HOC)是react中對組件邏輯進行重用的高級技術。但高階組件本身并不是React API。它只是一種模式,這種模式是由react自身的組合性質必然產生的。
具體而言,高階組件就是一個函數,且該函數接受一個組件作為參數,并返回一個新的組件,高階組件會對傳入的組件做一些通用的處理。比如:我們希望給組件的的下方增加一個版權信息,那么就可以使用高階組件。把原始組件傳入,然后,給組件增加一個版權信息后,再返回。
高階組件是通過將原組件 包裹(wrapping) 在容器組件(container component)里面的方式來組合(composes) 使用原組件。高階組件就是一個沒有副作用的純函數(有參數,有返回值,并且在函數內部不會修改參數)。
如:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
高階函數是:higherOrderComponent
傳入的組件是:WrappedComponent
返回的組件是:EnhancedComponent
示例代碼:
// 帶上版權的高階函數,并且給組件賦能(增加屬性和方法)
function withCopyRight(OldCom){
? ?//給組件賦能
? ?OldCom.prototype.aaa = "hi";
? ?OldCom.prototype.fn = function(){
?
? };
? ?
? ?class NewCom extends React.Component{ ? ? ?
? ? ? ?render() {
? ? ? ? ? ?return (
? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? Copyright ? 2020 Sohu All Rights Reserved. 搜狐公司 版權所有
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ?
? ? ? ? ? );
? ? ? }
? } ?
? ?
? ?return NewCom;
}
?
//原始組件
class CommentList extends React.Component {
? ?componentDidMount(){
? ? ? ?console.log("this.aaa",this.aaa);
? ? ? ?console.log("this.fn",this.fn);
? }
? ?render() { ? ? ?
? ? ? return (
? ? ? ? ?
? ? ? ? ? ? ? 新聞11111111111111111111
? ? ? ? ?
? ? ? );
? }
}
?
//調用高階函數后的組件
const CommentListWithCopyRight = withCopyRight(CommentList);
?
ReactDOM.render(
function $(id){
? ?return document.getElementById(id);
}
高階組件的優(yōu)點:
1、給組件賦能 :如:給類的props或者state增加數據和方法,或者給類的prototype增加數據和方法。
2、解耦
3、開閉原則:對修改關閉,對擴展開放
高階組件的注意點(這些特點慢慢理解消化,使用其做了項目后,回過頭來再看一下):
1、不要在render函數里使用(調用)高階組件
2、必須將靜態(tài)(static)方法做拷貝
當使用高階組件包裝組件,原始組件被容器組件包裹,也就意味著新組件會丟失原始組件的所有靜態(tài)方法,解決這個問題的方法就是,將原始組件的所有靜態(tài)方法全部拷貝給新組件。
3、Refs不能傳遞
一般來說,高階組件可以傳遞所有的props屬性給包裹的組件,但是不能傳遞refs引用。
4、不要在高階組件內部修改(或以其它方式修改)原組件的原型屬性(prototype)。
5、約定:將不相關的props屬性傳遞給包裹組件
6、約定:最大化使用組合
7、約定:包裝顯示名字以便于調試
路由
官網 中文
vue-routerreact-router路由配置分離式(在一個單獨的文件里配置)嵌套式(路由配置在組件內部,任何組件都可以寫)匹配排他性(只有一個路由被匹配上)包容性(多個路由可能會同時被匹配上)路由上下文this.$router,this.$route掛在組件屬性上的history,location,match路由對象需要專門創(chuàng)建不需要專門創(chuàng)建,都是組件的方式使用,相關對象掛在了組件的屬性上
1、作用:
路由最基本的職責就是當頁面的 訪問地址 與 Route 上的 path 匹配上時,就渲染出對應的 UI 界面(組件)。
實現SPA應用,整個項目只有一個完整頁面。頁面切換不會刷新頁面,內容局部更新
2、react-router提供了兩種路由模塊
1).React-Router:
提供了一些router的核心API,包括Router, Route, Switch等,但是它沒有提供 DOM 操作進行跳轉的API。很少用
2).React-Router-DOM:
提供了 BrowserRouter,HashRouter , Route, Link,Switch等 API,我們可以通過 DOM 的事件控制路由。例如點擊一個按鈕進行跳轉,所以在開發(fā)過程中,我們更多是使用React-Router-DOM。
3、路由的兩種模式:
1).HashRouter組件:
url中會有個#,例如localhost:3000/#,HashRouter就會出現這種情況,它是通過hash值來對路由進行控制。如果你使用HashRouter,你的路由就會默認有這個#。
2).BrowserRouter組件:
很多情況下我們則不是這種情況,我們不需要這個#,因為它看起來很怪,這時我們就需要用到BrowserRouter。
記?。喝サ舳嘤嗟腂rowserRouter 標簽,整個項目中,寫一個BrowserRouter 標簽就行,否則,react路由時,不能正常渲染,需要刷新,才能渲染。
4、路由的配置
1). Route組件:
vue的路由配置是分離式的,在一個單獨的文件里進行路由配置。react是嵌套式的,路由配置就寫在組件內部,任何組件都可以寫。
vue中是在json數組里寫好配置,然后傳入vueRouter對象,而React直接使用組件BrowserRouter(或HashRouter)和Route進行配置。
Route:路徑和組件的對應關系。職責就是當頁面的訪問地址與 Route 上的 path 匹配時,就渲染出對應的組件
如:
?
?
5、路由匹配到的組件展示在何處
也是使用Route組件。即:Route組件既是路由配置,又是渲染組件的地方。
6、路由的跳轉
聲明式導航 vue中使用Router-Link組件,react中使用Link或NavLink 組件進行路由跳轉導航
1).Link:
主要屬性是to,to可以接受string或者一個object,來控制url。
去About
去About
?
2).NavLink:
它可以為當前選中的路由設置類名、樣式以及回調函數等。to屬性跳轉路徑,activeClassName當元素處于活動狀態(tài)時應用于元素的樣式
編程式導航
?
this.props.history.push({pathname:'/about'})
?
注意:
如果當前組件不是通過路由跳轉過來的,那么,當前組件的props里,沒有history。
6、基本使用步驟:
1)、下載路由模塊:
npm install --save react-router-dom
2)、創(chuàng)建若干個組件
創(chuàng)建components文件夾,并建立若干個組件
如:About.js,InBox.js,Goodslist,
3)、路由配置:
src/index.js
//引入模塊
import { BrowserRouter, Route } from "react-router-dom";
?
ReactDOM.render(
?
? ?
? ?
? ?
?,
?document.getElementById('root')
);
4)、路由跳轉
聲明式導航
在App.js里做鏈接跳轉
src/app.js
?
import { Link } from 'react-router-dom'
?
function App() {
?return (
? ?
? ? ?
- About
- Inbox
? ? ? ? ?
? ? ? ? ?
? ? ? ?
? ?
);
}
擴展:
可以嘗試把 app.js里的 link 標簽換成a標簽??纯错撁媸遣皇菚虚W動。
(PS:如果使用a標簽,在每次點擊時,頁面被重新加載,標簽會避免這種狀況發(fā)生。當 你點擊時,url會更新,組件會被重新渲染,但是頁面不會重新加載)
二級路由
直接在InBox組件里在寫路由配置就行:
? ?
? ?
鉛筆 |
橡皮
? ?
? ?
? ?
? ?
7、路由傳參
路由傳參完成的是組件之間的數據傳遞(組件傳值)
1)、params
路由配置:
路由跳轉(傳值):
聲明式導航:
編程式導航:
this.props.history.push(?'/Inbox/01008' )
取值(接值):
this.props.match.params.id
優(yōu)勢 : 刷新,參數依然存在
缺點 : 只能傳字符串,并且,如果傳的值太多的話,url會變得長而丑陋。
場景:傳遞一個參數時使用。
2)、query
路由配置:
不用改變路由配置表。
路由跳轉(傳值):
聲明式導航:
鉛筆
編程式導航:
this.props.history.push(?{pathname:'/Inbox',query:{id:'01009'}} )
取值:
this.props.location.query.id
優(yōu)勢:傳參優(yōu)雅,傳遞參數可傳對象;
缺點:刷新地址欄,參數丟失
3)、state
同query差不多,只是屬性名不一樣,而且state傳的參數是加密的,query傳的參數是公開的,只需要把query改為state即可。
路由配置:
不用改變路由配置表。
路由跳轉(傳值):
聲明式導航
鉛筆
編程式導航
this.props.history.push({pathname:'/Inbox',state:{id:"01"}});
取值:
this.props.location.state.id
優(yōu)勢:傳參優(yōu)雅,傳遞參數可傳對象
缺點:刷新地址欄,(hash方式會丟失參數,Browser模式不會丟失參數)
4)、search
路由配置:
不用改變路由配置表。
傳值:
聲明式導航
鉛筆
編程式導航
this.props.history.push({pathname:'/Inbox',search:'?a=1&b=2'})
取值:
this.props.location.search
用location.search所獲取的是查詢字符串(如:?a=1&b=2),所以,還需要進一步的解析,自己自行解析,也可以使用第三方模塊:qs,或者nodejs里的query-string
8、路由上下文
在react-router里面,如果組件是通過路由跳轉的,那么它會把路由相關的API掛在了組件的props上(vue-Router使用的$router和$route),并且分為history,location,match。
history:歷史,用來跳轉的,并做歷史記錄。有函數:push(),replace(),go() …………
location:地址,地址欄上路徑,并保存著query,state,search等數據。
match:匹配,匹配到的路徑,有params
9、非路由跳轉的組件(標簽的方式)如何獲取路由上下文
先看一下,組件在頁面上展示的兩種情況下,對應的props對象的內容
1)、標簽名的方式直接展示的組件(沒有屬性),props是空
2)、路由跳轉的方式展示的組件(就算沒有屬性),props會自動增加屬性:history,match,location。
如果用標簽名的方式,還想獲取到路由上下文,有以下解決方案:
通過屬性傳遞 首先要求,當前組件是路由跳轉過來的,然后把路由上下文通過屬性的方式傳遞給子組件
import {withRouter} from 'react-router-dom'
?
class 組件 extends Component{
? ?
}
?
export default withRouter(組件)
exact屬性:
react的路由匹配(path后的路徑和地址欄路徑)默認是模糊的(path后面的路徑只要包含在地址欄的路徑里就能匹配上對應的組件)
如果想使用嚴格匹配(path后面的路徑和地址欄的路徑完全相同),那么,把Route組件的exact屬性設置為true。
假如,有如下路由配置:
? ?
? ?
地址欄中輸入:
http://localhost:3000/My
那么路徑 “/My”,匹配到的路徑是: “/” 和 “/My”,并且,在瀏覽器會把匹配到的所有組件的內容進行顯示。
如果希望 路徑 /My 值匹配 path=“/My”,那么,這么寫:
404
在路由配置里不設置path屬性,那么,就總是會匹配上。404頁面就需要這樣做(當然還得結合Switch)
Switch
排他性匹配。
react默認的路由匹配是包容性的,即:匹配到的多個路由對應的所有組件會同時被顯示在頁面上。如果只希望顯示匹配到的第一個組件(換句話說:匹配到的第一個符合要求的路徑后,其它路徑就不再做匹配),那么使用switch。
假如,有如下路由配置:
? ?
? ?
? ?
? ?
地址欄中輸入:
http://localhost:3000/My
那么路徑 “/My”,匹配到的路徑是:“/My” 和最后一個
路由配置改成如下:
?
? ?
? ? ?
? ? ?
? ? ?
? ?
?
Switch應該寫在Route的外層,而且,在Switch和Route中間不能有任何組件
地址欄中輸入:
http://localhost:3000/My
那么路徑 “/My”,只會讓瀏覽器顯示匹配到的第一個組件My。
區(qū)分:exact和switch
exact:表示路徑匹配規(guī)則,exact={true} 表地址欄的路徑和 路由配置中path一定要完全相等
switch:表示排他性,即:一旦地址欄上路徑和某個path匹配成功后,就不再匹配其它path。
Redirect
附:路由提供組件的詳解(自行研究)
組件及其作用:
組件作用路由模式BrowserRouter約定模式 為 history,使用 HTML5 提供的 history API 來保持 UI 和 URL 的同步路由模式HashRouter約定模式 為 hash,使用 URL 的 hash 來保持 UI 和URL 的同步聲明式跳轉NavLink聲明式跳轉 還可以約定 路由激活狀態(tài)聲明式跳轉Link聲明式跳轉 無激活狀態(tài)重定向Redirect重定向 ~~ replace匹配并展示Route路由配置和展示。匹配組件,并展示組件。即匹配成功后,
結構
BrowserRouter|HashRouter (整個項目里只能有一個BrowserRouter|HashRouter ) App(或其它組件)
NavLink|Link Route(外層可以包裹Switch) Redirect
子組件
NavLink|Link Route ...
BrowserRouter
屬性類型作用basenamestring所有位置的基本URL。如果您的應用是從服務器上的子目錄提供的,則需要將其設置為子目錄。格式正確的基本名稱應以斜杠開頭,但不能以斜杠結尾getUserConfirmationFunction用于確認導航的功能。默認使用window.confirm。
Route
屬性類型作用pathstring |object路由匹配路徑。沒有path屬性的Route 總是會 匹配exactboolean為true時,要求全路徑匹配(/home)。路由默認為“包含”的(/和/home都匹配),這意味著多個 Route 可以同時進行匹配和渲染componentFunction |component在地址匹配的時候React的組件才會被渲染,route props也會隨著一起被渲染renderFunction內聯(lián)渲染和包裝組件,要求要返回目標組件的調用
Link
屬性類型作用tostring | 對象{pathname:,search:,hash:}要跳轉的路徑或地址replaceboolean是否替換歷史記錄
NavLink
屬性類型作用tostring|對象{pathname:,search:,hash:}要跳轉的路徑或地址replaceboolean是否替換歷史記錄activeClassNamestring當元素被選中時,設置選中樣式,默認值為 activeactiveStyleobject當元素被選中時,設置選中樣式
Switch
該組件用來渲染匹配地址的第一個Route或者Redirect,僅渲染一個路由,排他性路由,默認全匹配(場景:側邊欄,引導選項卡等)
屬性類型作用locationstring objectchildrennode
Redirect
該組件用來渲染匹配地址的第一個Route或者Redirect,僅渲染一個路由,排他性路由,默認全匹配(場景:側邊欄和面包屑,引導選項卡等
屬性類型作用fromstring來自tostring object去向pushboolean添加歷史記錄exactboolean嚴格匹配sensitiveboolean區(qū)分大小寫
補一下:async和await
概念:
async和await是回調地獄的終極解決方案,是ES7新增的兩個關鍵字。
async:異步
await:等待
功能:
將異步操作按同步操作的方式書寫,即上一步未執(zhí)行完,會阻塞當前函數的線程,不影響主線程
知識點:
1)、async:修飾的函數表示函數里面有異步操作,返回值是Promise對象
2)、await:
2.1)、await必須放在async修飾的函數里。
2.2)、await修飾代碼后,await所在行的后面的行的代碼會等待await修飾的代碼執(zhí)行完畢。
2.3)、一般來說,await后跟 promise對象,或者返回Promise對象的函數;
2.4)、await 修飾函數(或者promise對象)后,那么,返回值變成了Promise對象中resolve的參數;
如 Let res = await fn(); //res是函數fn返回的Promise對象里的resolve的參數。
2.5)、如果要拿到reject里參數,就使用try catch。
如:
try{
?
Let res = await fn(); //res是 fn函數里Promise的resolve的參數
?
}catch(err){
?
err 是 fn函數里Promise的reject的參數
?
}
完整代碼:
原理使用promise的axios代碼:
?
created(){ ?
? ?axios({
? ? ? ?url:"/books",
? ? ? ?method:"get",
? ? ? ?params:{
? ? ? ? ? ?type:this.type
? ? ? }
? })
? .then(res=>{
? ? ? ?this.books = res.data; ? ? ? ?
? })
? .catch(err=>{
? ? ? ?console.log("服務器出錯");
? }) ? ? ?
},
?
經過async和await改造后的代碼(沒有了回調);
?
async created(){ ?
? ?try {
? ? ? ?// 1、發(fā)送請求
? ? ? ?let res = await axios({
? ? ? ? ? ?url:"/books",
? ? ? ? ? ?method:"get",
? ? ? ? ? ?params:{
? ? ? ? ? ? ? ?type:this.type
? ? ? ? ? }
? ? ? })
?
? ? ? ?//2、把獲得的數據賦給變量
? ? ? ?this.books = res.data;
?
? } catch (error) {
? ? ? ?console.log("服務器出錯",error); ?
? }
}
如果想看細節(jié),請查看以下兩篇文章:
async和await的理解
原生JS面試題:async和await_原生js代碼的await-CSDN博客
原生JS面試題:什么是async,什么是await,async和await的區(qū)別,async和await的理解
原生JS面試題:什么是async,什么是await,async和await的區(qū)別,async和await的理解_js await和async-CSDN博客
選擇器沖突解決方案
一、命名空間 BEM
BEM(Block, Element, Modifier)是由Yandex團隊提出的一種前端命名規(guī)范。其核心思想是將頁面 拆分成一個個獨立的富有語義的塊(blocks)。在某種程度上,BEM和OOP是相似的。 Block:代表塊(Block):也是模塊的意思 Element:元素(Element): Modifier: 修飾符(Modifier) 無論是什么網站頁面,都可以拆解成這三部分
滑動驗證頁面
二、模塊化
import 變量 ?from './css/xx.module.css'
?
? //配置1 (腳手架默認配置上了) //webpack配置 "style-loader!css-loader?modules" | module:true //問題:所有css都需要模塊化使用 ? //配置2 //改名xx.css -> xx.module.css //需要模塊化的才修改,不影響其他非模塊化css寫法 三、scss 1、安裝: node-sass 2、sass文件 /*定義scss*/ $bg-color: #399; ? .box{ ?background: $bg-color; } ? 引入sass ? //1)、普通引入 import 'xx/xx.scss' ? ? ? ? ? //2)、模塊化 import style from xx.module.scss ? ? ? 引入全局的(公共的)scss 局部scss文件內部: @import './全局.scss' webpack配置一次,局部scss內部直接使用 //1. 安裝插件 : sass-resources-loader //2. 配置修改webpack.config.js ? { ?test:sassRegex, ?... ?use: [ ? {loader:'style-loader'}, ? {loader:'css-loader'}, ? {loader:'sass-loader'}, ? { ? ? ?loader: 'sass-resources-loader', ? ? ?options:{ ? ? ? ?resources:'./src/xx/全局主題.scss' ? ? } ? } ] } ? 注意: loader:'css-loader?modules' ?modules 模塊化時需要添加 resources 指向作用域在項目環(huán)境下 react hooks(重中之重) Hooks 介紹 Hook 簡介 – React react hooks是v16.8新增的特性, 他允許你 在不寫類組件(即:函數式組件)的情況下操作state 和react的其他特性(如:生命周期的鉤子函數)。 hooks 只是多了一種寫組件的方法,使編寫一個組件更簡單更方便,同時可以自定義hook把公共的邏輯提取出來,讓邏輯在多個組件之間共享。 Hooks 是什么? Hook 是一個特殊的函數,它可以讓你“鉤入” React 的特性。例如,useState 是允許你在 React 函數組件中添加 state 的 Hook。 以前,函數式組件里面沒有state,所以,無狀態(tài)組件我們用函數寫,或者說函數式組件是無狀態(tài)組件。而現在有了Hook后,函數式組件里,也可以使用state了。當然還有其它Hook。 使用規(guī)則 Hook可讓您在不編寫類(組件)的情況下使用狀態(tài)(state)和其他React功能 只能在頂層調用Hooks 。不要在循環(huán),條件或嵌套函數中調用Hook 只能在functional component或者自定義鉤子中使用Hooks 鉤子在類內部不起作用,沒有計劃從React中刪除類 useState (使用狀態(tài)): 格式: 1、定義狀態(tài): const [狀態(tài)名,更新狀態(tài)的函數] = React.useState(初始值|函數); ? 如: 1)、基本類型的狀態(tài) 聲明一個新的叫做 “count” 的 state 變量,初始值為0 。 ? const [count, setCount] = React.useState(0); //useState函數返回的是數組 const [name, setName] = React.useState('鄭天鴻'); //useState函數返回的是數組 ? ? 相當于類組件中的 this.state={ ? ?count :0 } ? 2)、引用類型的狀態(tài) const [person, setPerson] = React.useState({name: '張三瘋', age: 18,sex:"女"}) const [person, setPerson] = React.useState(() => ({name: '張三瘋', age: 18,sex:"女"})) ? 2、讀取值: {count} {person.name} ? {person.age} ? 3、修改值: ? ?setCount(5); ? ?//對于引用類型,不能局部更新(即:不能只改某個屬性),所以,需要使用擴展運算符先拷貝以前所有的屬性 ?setPerson({ ? ? ...person, //拷貝之前的所有屬性 ? ? age:person.age+1, ? ? name: '張四瘋' //這里的name覆蓋之前的name }) 注意: 首先,需要知道,函數式組件重新渲染時,會執(zhí)行函數體里的所有代碼, 那么,當函數式組件重新渲染時,會不會再次把狀態(tài)的值恢復成初始值呢?答案是:不會。后續(xù)組件重新渲染時,會使用最后一次更新的狀態(tài)值 【官網解釋: React 會確保 setState 函數的標識是穩(wěn)定的,并且不會在組件重新渲染時發(fā)生變化 】 示例代碼: import React,{useState} from 'react'; ? function App() { // 聲明一個叫 "count" 的 state 變量 ?const [count,setCount] = useState(0); //在App組件重新后,useState 返回的第一個值將始終是更新后最新的 count。 ? ?return ( ? ? ? ? ? {count} ? ? ?{setCount(count+1)}} /> ? ? ); } 對應的函數class組件: class App extends React.Component { ?state = { ? ? ?count:0 } ?render = () => ( ? ? ? ? ? {this.state.count} ? ? ? onClick={()=>this.setState({count:this.state.count+1})} /> ? ?
)
}
我們之前把函數式的組件叫做“無狀態(tài)組件”。但現在我們?yōu)樗鼈円肓耸褂?React state 的能力
再如:
?
function App() {
?const [person, setPerson] = React.useState({name: '張三瘋', age: 18})
?const onClick = () =>{
? ?//setPerson不可以局部更新,如果只改變其中一個,那么整個數據都會被覆蓋,所以,需要使用擴展運算符先拷貝以前所有的屬性
? ?setPerson({
? ? ? ?...person, //拷貝之前的所有屬性
? ? ? ?age:person.age+1,
? ? ? ?name: '張四瘋' //這里的name覆蓋之前的name
? })
}
?
?return (
? ?
? ? ? ?
name:{person.name}
? ? ? ?
age:{person.age}
? ? ? ?
? ?
);
}
useEffect 處理副作用 (生命周期鉤子函數)
可以使得你在函數組件中執(zhí)行一些帶有副作用的方法,天哪,“副作用”(大腦中無數個????)。
每當 React組件更新之后,就會觸發(fā) useEffect,在第一次的render 和每次 update 后,useEffect都會觸發(fā),不用再去考慮“初次掛載”還是“更新”。React 保證了每次運行 effect 的同時,DOM 都已經更新完畢。
你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數的組合。
我們在函數式組件里,沒有 componentDidMount,componentDidUpdate 和 componentWillUnmount,用useEffect。即:當數據發(fā)生變化后,渲染到組件上,組件渲染完畢后,就會調用useEffect。
格式:
useEffect(回調函數,[依賴]); //在render之后觸發(fā)useEffect,進一步調用回調函數
1、useEffect的無條件執(zhí)行(只有一個參數)
import React,{useState,useEffect} from 'react';
?
function App() {
?const [count,setCount] = useState(0);
?
?//useEffect:相當于 componentDidMount,componentDidUpdate
?useEffect(()=>{
? ? ?console.log("userEffect");
? ? ?document.title = count;
});
?
?return (
? ?
? ? ?
{count}
? ? ?{setCount(count+1)}} />
? ?
);
}
2、useEffect的條件執(zhí)行(useEffect的第二個參數)
當useEffect只有一個參數時,會無條件執(zhí)行,那么,當發(fā)送請求時(頁面的初始數據來自后端),一旦把請求放在useEffect里,就會無休止的執(zhí)行。因為,當請求的數據回來后,引起組件的更新,組件更新后,再次觸發(fā)useEffect,再次發(fā)送請求,再次組件更新………………,陷入到了無限的死循環(huán)。怎么辦呢?可以使用useEffect的第二個參數。
第二個參數表示:useEffect是否再次觸發(fā) 是 依賴于某個狀態(tài)的變化。當為空數組時,表示不會二次觸發(fā)。即:componentDidMount時會觸發(fā),componentDidUpdate不會觸發(fā)。
換種解釋方式:
1、這是格式: useEffect(回調函數,依賴的數組)
2、數組里是回調函數是否執(zhí)行的條件,(即:數組里的狀態(tài)發(fā)生變化時,才調用回調函數)
3、如果是空數組,表示任何狀態(tài)發(fā)生變化,都不會調用回調函數。相當于componentDidMount
如下代碼,由于依賴是空,所以,useEffect只表示componentDidMount。
useEffect( async ()=>{
? ? ?let data = await getBooks(); ?//發(fā)送請求的代碼已經封裝 ? ?
? ? ?setBooks(data); ? ? ?
},[]);
如下代碼,表示componentDidMount,和 count變化后引起的componentDidUpdate。
useEffect( async ()=>{
? ? ?let data = await getBooks(); ?//發(fā)送請求的代碼已經封裝 ? ?
? ? ?setBooks(data); ? ? ?
},[count]);
useContext(使用狀態(tài)樹傳參)
Context狀態(tài)樹的使用,比較復雜,特別是使用Consumer時。
useContext這個hook能讓Context使用起來變得非常簡單。不需要再使用Consumer。使用useContext就能拿到context狀態(tài)樹里的值。
const value = useContext(context對象);
useContext函數的解釋:
參數: context 對象(React.createContext 的返回值)
返回值: context對象的當前值(由上層組件中距離當前組件最近的 的 value prop 決定)
當組件上層最近的 Provider 更新時,該 Hook 會觸發(fā)重新渲染 。
示例:
//context/index.js 創(chuàng)建context對象。
?
import {createContext} from "react";
?
export default createContext({count:0});
?
//主入口文件 index.js
?
import ReactDOM from 'react-dom';
import App from './App';
import Context from "./context"
?
let count = 10;
?
ReactDOM.render(
? ?
? ?
? ?,
?document.getElementById('root')
);
?
?
//孫子組件 App-->User-->UserAdd.js
?
import myContext from "../../../context";
import {useContext} from "react";
?
export default (props)=>{
? ?let context = useContext(myContext);
? ?console.log(context);
? ?return (
? ? ? ?
? ? ? ? ? ?
我是用戶添加頁面
? ? ? ? ? ?
count:{context}
//此處使用比起consumer是不是簡單的多得多得多了呢?? ? ? ? ? ?
? ? ? ?
? );
}
useCallBack
一、概念和作用
1、memo高階函數:
memo解決的是函數式組件的無效渲染問題,當函數式組件重新渲染時,會先判斷數據是否發(fā)生了變化。相當于類組件的PureComponent(默認提供ShouldComponentUpdate)
2、useCallback:
1)、useCallback會返回一個函數的memoized(記憶的)值
2)、在依賴不變的情況下,多次定義(如:函數)的時候,返回的值是相同的 。
3)、格式:
let 新的函數 = useCallback(曾經的函數, [依賴的值])
二、使用場景:
1、memo高階函數的使用場景:
不論父組件是什么類型的組件,子組件是否渲染 :
1)、 如果子組件是類組件(繼承自PureComponent)
那么是否渲染由props和state是否改變決定;
2)、如果子組件是函數式組件
只要父組件渲染,子組件會無條件渲染。如下是代碼示例:
//父組件:
?
import { useState } from "react";
import SonFn from "./SonFn";
?
export default () => {
? ?console.log("父組件");
? ?const [count, setCount] = useState(1);
?
? ?let changeCount = () => {
? ? ? ?setCount(count + 1);
? }
?
? ?return (
? ? ? ?<>
? ? ? ? ? ?
useCallback
? ? ? ? ? ?
{count}
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ?>
? )
}
?
//子組件:
./SonFn.js
?
export default ()=>{
? ?console.log("子組件");
? ?return (
? ? ? ?
? ? ? ? ? ?
子組件(函數式組件)
? ? ? ?
? )
} ?
只要點擊按鈕"修改count”,父組件就會刷新,而子組件SonFn也會無條件渲染(這是無效的渲染)。
3)、解決方案:
把子組件用高階函數memo進行包裹,就能解決子組件的無條件渲染問題,即:子組件的渲染就會由props和state決定,有點像類組件繼承自PureComponent的感覺。
如下是代碼(只需要把子組件的代碼進行修改就行):
//子組件:
import React,{memo} from 'react'
?
const SonFn = ()=>{
? ?console.log("子組件");
? ?return (
? ? ? ?
? ? ? ? ? ?
子組件(函數式組件)
? ? ? ?
? )
}
?
export default memo(SonFn);
2、useCallback的使用場景:
父組件是函數式組件,子組件也是函數式組件(并且用memo包裹)
1)、子組件的屬性是數據:
如果數據不變化,那么子組件不渲染,如果數據發(fā)生變化,那么子組件渲染。這里就沒有性能問題。
//父組件
?
import { useState,useCallback } from "react";
import SonFn from "./SonFn";
?
export default () => {
? ?console.log("父組件UseCallback");
? ?const [count, setCount] = useState(1);
?
? ?let changeCount = () => {
? ? ? ?setCount(count + 1);
? }
?
? ?return (
? ? ? ?<>
? ? ? ? ? ?
useCallback1
? ? ? ? ? ?
{count}
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? {/*此處給子組件傳入了數據count,count只要發(fā)生變化,子組件就會重新渲染*/}
? ? ? ? ? ?
? ? ? ?>
? )
}
?
//子組件:
./SonFn.js
?
import React,{memo} from 'react'
?
const SonFn = ({count})=>{
? ?console.log("子組件");
? ?return (
? ? ? ?
? ? ? ? ? ?
子組件(函數式組件)
? ? ? ? ? ?
{count}
? ? ? ?
? )
}
?
export default memo(SonFn);
?
?
2)、子組件的屬性是函數(其實,只要是引用類型)時,就會出現問題(無效渲染):
父組件刷新(重新渲染)了,子組件依然會刷新(重新渲染)。因為,父組件(函數式)每次刷新時,函數都會重新定義,那么傳給子組件的函數屬性必然會發(fā)生變化。所以子組件會刷新,如下是示例代碼:
//父組件:
?
import { useState } from "react";
import SonFn from "./SonFn";
?
export default () => {
? ?console.log("父組件");
? ?const [count, setCount] = useState(1);
?
? ?let changeCount = () => {
? ? ? ?setCount(count + 1);
? }
?
? ?let increment = ()=>{
? ? ? ?console.log("increment");
? }
?
? ?return (
? ? ? ?<>
? ? ? ? ? ?
useCallback
? ? ? ? ? ?
{count}
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ?>
? )
}
?
//子組件:
./SonFn.js
?
import React,{memo} from 'react'
?
const SonFn = ()=>{
? ?console.log("子組件");
? ?return (
? ? ? ?
? ? ? ? ? ?
子組件(函數式組件)
? ? ? ?
? )
}
?
export default memo(SonFn);
?
3)、解決方案:把傳給子組件的函數屬性,用useCallback包裹。
格式:
let 新的函數 = useCallback(曾經的函數, [依賴的值])
如下是修改后的代碼(只需要修改父組件的代碼):
以下代碼把increment函數進行了包裹
export default () => {
? ?console.log("父組件");
? ?const [count, setCount] = useState(1);
?
? ?let changeCount = () => {
? ? ? ?setCount(count + 1);
? }
?
? ?let increment = useCallback(()=>{
? ? ? ?console.log("increment");
? },[]) // 該函數永遠不會重新定義(第二個參數空數組表示,任何數據發(fā)生變化,回調函數都不會再次定義)
? ?
? ?/*
? let increment = useCallback(()=>{
? ? ? console.log("increment");
? },[count]) // 當count的值發(fā)生變化是,該函數才會重新定義
*/
? ?
? ?return (
? ? ? ?<>
? ? ? ? ? ?
useCallback
? ? ? ? ? ?
{count}
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ? ? ?
? ? ? ?>
? )
}
三、總結:
1、“萬惡之源” :函數式組件每次重新渲染時,都會把函數體里的所有代碼執(zhí)行一遍。
2、useCallback解決的是 防止函數式組件里的 子函數(閉包) 多次被定義。既就是:useCallback是保證函數式組件重新渲染時,組件里的函數(閉包)只被定義一次。
useCallback 記憶函數(詳細版的)
react hooks系列_useCallback_react hooks usecallback-CSDN博客
useMemo 記憶組件
?//格式
?useMemo(函數,數組);
?
?//意思:
?// 當數組中的其中一個元素,發(fā)生變化時,就會 調用 函數 。
如: const nameStr = useMemo(()=>genName(name),[name])
表示,當name發(fā)生變化時,才會調用 ()=>genName(name)函數
如: const nameStr = useMemo(()=>genName(name),[name,age])
表示,當name或者age發(fā)生變化時,都會調用 ()=>genName(name)函數
以下代碼中,如果不使用useMemo,當我們點擊“修改年齡”的按鈕時,也調用了函數genName()。這其實是性能的損耗。
import React,{useState,useMemo} from "react";
import './App.css';
?
?
function Person({ name, age}) {
? ?
?console.log("Person函數");
? ?
?function genName(name) {
? ?console.log('genName')
? ?return '姓名:'+name;
}
?
?let nameStr = genName(name); ?//沒有使用useMemo
? ?// 以下代碼有點vue中的watch的感覺:當name發(fā)生變化時,才調用回調函數.
// const nameStr = useMemo(()=>genName(name),[name]) //此處使用 useMemo
?
?return (
? ?<>
? ? ?
? ? ?
? ? ?
? ?>
)
}
?
?
function App() {
?const [name, setName] = useState('張三瘋')
?const [age, setAge] = useState(12)
?
?return (
? ?<>
? ? ?
? ? ?
? ? ?
? ? ?
? ?>
)
}
?
export default App;
區(qū)分useMemo和useCallback:
useCallback:解決的是:防止無效的函數定義
useMemo:解決的是:防止無效的函數調用
useRef 保存引用值
https://reactjs.bootcss.com/docs/hooks-reference.html#useref
useRef 返回一個可變的 ref 對象,其(ref 對象) .current 屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期內保持不變。
import {useRef} from "react";
?
let refContainer = useRef(initialValue) ?
?
? refContainer.current.dom操作 一個常見的用例便是命令式地訪問子組件: function TextInputWithFocusButton() { ? ?//定義了一個ref變量:inputEl ?const inputEl = useRef(null); ? ?const onButtonClick = () => { ? ?// `current` 指向已掛載到 DOM 上的文本輸入元素 ? ?inputEl.current.focus(); }; ? ? ?return ( ? ?<> ? ? ? ? ? ? ? ?> ); } 擴展: 為什么react選擇了函數式組件(剖析原理)_react 為什么推薦函數組建-CSDN博客 為什么react選擇了函數式組件(剖析原理) 面試題: //請問以下組件里,在執(zhí)行時,會打印的 function App() { ?console.log("app"); ?const [count,setCount] = React.useState(0); ? ?useEffect(()=>{ ? ?setInterval(()=>{ ? ? ? ?setCount(5) ? ? } ? ,1000) },[]); ? ?console.log("app:",count);//此次會打印多少:?? ? ?return ( ? ? ? ? ? {count} ? ?
);
}
?
后面的hooks,以后的以后有余力,再看吧
useLayoutEffect 同步執(zhí)行副作用
路由相關的hooks
在非路由跳轉的組件里,要獲取路由上下文對象,除了可以使用高階組件withRouter外,react-router-dom里還提供了hooks。
useHistory useLocation useParams useRouteMatch
useHistory():
useHistory 返回一個 路由上下文上的history 對象
import { useHistory } from "react-router-dom";
?
export default (props)=>{
? ?let history = useHistory();
? ?console.log("我是用戶添加頁面:props",props);
? ?return (
? ? ? ?
? ? ? ? ? ?
我是用戶添加頁面
? ? ? ? ? ?history.push("/")} />
? ? ? ?
? );
}
useLocation()
useLocation() 返回一個路由上下文的 location 對象。
用戶管理
?
import { useHistory,useLocation } from "react-router-dom";
?
export default ()=>{
? ?let location = useLocation();
? ?let history = useHistory();
? ?console.log("location",location);
? ?return (
? ? ? ?
? ? ? ? ? ?
我是用戶添加頁面
? ? ? ? ? ?history.push("/")} />
? ? ? ?
? );
}
useParams()
useParams() 返回當前匹配的路徑上的 params
用戶管理
import { useHistory,useLocation,useParams } from "react-router-dom";
export default ()=>{ let location = useLocation(); let history = useHistory(); let params = useParams();
console.log("location",location);
console.log("params",params);
?
return (
?
? ? ?
我是用戶添加頁面
? ? ? history.push("/")} />
?
);
}
useRouteMatch
useRouteMatch 可以有一個參數 path,如果什么都不傳,會返回當前 context 上的 match 的值,一定是 true。如果傳了 path,會比較這個 path 和當前 location 是否 match。
視情況手寫redux
狀態(tài)管理(重中之重)
思想:flux 實現:vuex redux
redux
1. Redux是為javascript應用程序提供一個可預測(給一個相同的輸入,必然會得到一個相同的結果)的狀態(tài)容器??梢赃\行于服務端,客戶端,原生應用,從Flux演變而來。簡單容易上手。
集中的管理react中多個組件的狀態(tài) redux是專門作狀態(tài)管理的js庫,并不是react的插件庫,也可以用在其他js框架中,例如vue,但是基本用在react中
可以同一個地方查詢狀態(tài),改變狀態(tài),傳播狀態(tài),用在中大項目,如下場景:組件狀態(tài)需要共享,在任何地方都可以拿到,組件需要改變全局狀態(tài),一個組件需要改變另外一個組件的狀態(tài)。創(chuàng)建store實例,其它組件導入并共享這個store實例
redux成員
成員作用類型createStore(reducer,state)創(chuàng)建store實例(reducer:對數據的操作,state:倉庫存放的數據)函數combineReducers合并多個reducer函數applyMiddleware安裝中間件,改裝增強redux函數
store成員
成員作用類型subscribe(回調函數)訂閱state變化函數dispatch(action)發(fā)送action 給 reducer函數getState()獲取一次state的值函數replaceReducer一般在 Webpack Code-Splitting 按需加載的時候用函數
數據流動
component(views)actionreducerstatecomponent(views)展示state轉發(fā)的動作,異步業(yè)務同步業(yè)務處理邏輯, 修改state,并且返回新的state狀態(tài)收集store.dispatch---》-------------》《--subscribe《--getState
安裝:
yarn add redux
示例代碼:
1)、組件從store中獲取state(數據)):
//1、創(chuàng)建一個store,
//創(chuàng)建store需要reducer和state。即就是:創(chuàng)建倉庫時,需要說清楚倉庫中存儲的數據(state),以及對數據的操作(reducer)
?
./src/store/index.js
?
import { createStore} from "redux";
import state from "../store/state";
import reducer from "../store/reducer";
?
//1、 創(chuàng)建倉庫,并且說清楚,倉庫里存儲的數據,以及,對倉庫數據的操作
// createStore :創(chuàng)建倉庫的
// state:倉庫里存儲的數據
// reducer:對倉庫數據的操作
let store = createStore(reducer,state);
?
export default store;
?
//2、創(chuàng)建state
//state就是倉促存儲的數據(對象)
./src/store/state.js
?
export default {
? ?count:0
}
?
//3、創(chuàng)建reducer
//對倉庫數據進行操作的函數(函數)。
//要求:傳入舊的state,返回新的state。
?
./src/store/reducer.js
?
// reducer要求是個純函數(在函數內部不能修改函數的參數(輸入),要有返回值),它的功能是:傳入舊的state,根據action對state做操作,返回新的state。
// 參數:
// state:原始的state
// action:要做的事情,動作的類型
// 返回值:必須要有,是新的state(修改后的state)。getState()函數會調用reducer
?
// 因為是純函數,所以,在函數內部,不能修改state和action。
let reducer = (state,action)=>{
? ?if(action.type){
? ? ?//對state的操作
? }
? ?switch(action.type){
? ? ? ?case 添加:……………………return 新的state;break; ? ? ?
? ? ? ?case 刪除:……………………return 新的state;break; ? ? ?
? }
? ?return state;//返回新的state
}
?
export default reducer;
?
?
//組件
./src/App.js
import store from "./store/index";
?
class App extends React.Component {
?state = {
?
}
?render = () => (
? ?
? ? ?
? ? ? ?
{store.getState().count}
? ? ?
? ?
)
}
注意:getState()函數內部也調用了reducer。即:getState()獲取的值是reducer的返回值。
2)、通過組件修改store中的state(數據)):
注意:修改store中的state(數據)后,還需要把數據響應到組件上,就需要使用 subscribe,并使用組件中狀態(tài)(否則,組件不會重新渲染)。
修改reducer
./src/store/reducer.js
?
let reducer = (state,action)=>{
? ?let {type,payload} = action;
? ?switch(type){
? ? ? ?case "INCREMENT":{
? ? ? ? ? ?console.log("加一");
? ? ? ? ? ?return {
? ? ? ? ? ? ? ?...state,
? ? ? ? ? ? ? ?count:state.count+payload
? ? ? ? ? }
? ? ? }
? ? ? ?default:return state;
? }
}
?
//修改App組件的代碼
./src/App.js
class App extends React.Component {
?constructor(props){
? ?super(props);
? ?this.state = {
? ? ? ? count:store.getState().count
? }
?
? ?store.subscribe(()=>{
? ? ? ?this.setState({
? ? ? ? ?count:store.getState().count
? ? ? });
? })
}
?
? ?inc(){
? ? ? ?store.dispatch({type:"INCREMENT",payload:10});
? }
?
? ?render = () => (
? ? ?
? ? ? ?
? ? ? ? ?
{this.state.count}
? ? ? ? ?{this.inc()}} /> ?
? ? ? ?
? ? ?
? )
}
操作流程總結
安裝:
yarn add redux
?
import {createStore} from 'redux'
?
//一、創(chuàng)建reducer
//reducer:對倉庫(數據)的操作
//參數:
// state:傳入的舊數據(原始數據)
// action:對數據的操作
//返回值:操作后的新的數據
?
const reducer = (state,action)=>{
?let {type,payload}=action ? ?
?swtich (type){
case XXXXX :{
? ?//數據的邏輯處理
? ? return {
? ? ? ? ?...state,
? ? ? ? ?屬性名:新的值
? ? }
} ?
default:
? ?return state
}
}
?
//二、創(chuàng)建state對象
// 倉庫里的數據
export default {
? ?count:0
}
?
//三、創(chuàng)建store對象(倉庫)
//使用createStore(對倉庫的操作,倉庫的數據)
store = createStore(reducer,state)
export default store;
?
//四、在組件內部使用倉庫(如:獲取倉庫的數據,修改倉庫的數據,添加,刪除)
?
import store from '...'
?
store.getState() //獲取狀態(tài),執(zhí)行一次
?
store.dispatch({type:xxx,payload:ooo}) //發(fā)送action給reducer type是必傳參數
?
store.subscribe(回調) ?//訂閱 state 更新state時觸發(fā)
提取并定義 Action
./src/store/actions.js
?
export const increment = payload =>({
? ?type:"INCREMENT",
? ?payload
})
?
export const decrement = payload =>({
? ?type:"DECREMENT",
? ?payload
})
?
App.js組件
./src/App.js
?
import { increment } from "./store/actions";
?
store.dispatch(increment(10));
action里處理異步
需要安裝中間件 redux-thunk ,redux-thunk可以增強dispatch函數的功能:讓dispatch可以接受一個函數作為參數。
./src/store/index.js
?
//安裝中間件改裝 redux
import {createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
?
let store = createStore(reducer,state,applyMiddleware(thunk));
?
./src/store/actions.js
?
//處理異步
export let ADD =()=>((dispatch)=>{
? ?axios({
? ? ? ?url:"http://localhost:3000/inc",
? })
? .then(res=>{
? ? ? ?dispatch({
? ? ? ? ? ?type:"INCREMENT",
? ? ? ? ? ?payload:res.data.num
? ? ? })
? })
})
?
?
App組件
./src/App.js
?
import { increment,decrement,ADD } from "./store/actionCreators";
?
store.dispatch(ADD());
combineReducers提取reducer
當應用邏輯逐漸復雜的時候,我們就要考慮將巨大的 Reducer 函數拆分成一個個獨立的單元,這在算法中被稱為 ”分而治之“,拆分后的reducer 在 Redux 中實際上是用來處理 Store 中存儲的 State 中的某一個數據,一個 Reducer 和 State 對象樹中的某個屬性對應。 ? 把一個大的reducer拆成若干個小的。注意:把大的state也分開,并且放在每個reducer。也就是說,每個reducer里面寫上它要操作的數據,這樣的話,在一個reducer里包含了,數據(state)和數據的操作(reducer)。
// ./src/plugins/myRedux.js
?
import {createStore,applyMiddleware,combineReducers} from 'redux'
import thunk from 'redux-thunk'
import count from "../store/reducers/count";
import todos from "../store/reducers/todos";
?
let rootReducer = combineReducers({
? ?todos:todos,
? ?count:count
});
//去掉了第二個參數state(因為state拆分到了每個reducer里了)
export default createStore(rootReducer,applyMiddleware(thunk));
?
?
// ./src/store/reducers/count.js
?
// 是當前reducer要操作的數據
let initCount = 8;
?
const count = (count=initCount,action)=>{ ? ?
? ?switch(action.type){
? ? ? ?case "INCREMENT":{
? ? ? ? ? ?return count+action.payload;
? ? ? }
? ? ? ?case "DECREMENT":{
? ? ? ? ? ?return count-action.payload;
? ? ? }
? ? ? ?default: return count;
? }
}
?
export default count;
?
?
// ./src/store/reducers/todos
let initState=[] //當前reducer所操作的數據,放在里自己的模塊里。
?
const todos = (todos, action) => {
?switch (action.type) {
? ?case "ADD_TODO": {
? ? ?return [
? ? ? ?...todos,
? ? ? {
? ? ? ? ?id: action.id,
? ? ? ? ?text: action.text,
? ? ? ? ?completed: false
? ? ? }
? ? ]
? }
?
? ?case "REMOVE_TODO": {
? ? ?const { id } = action;
? ? ?todos.map((item,index) => item.id ===id && todos.splice(index, 1));
? ? ?return [...todos]
? }
?
? ?case "CHECK_TODO": {
? ? ?const { id } = action;
? ? ?todos.map((item,index) => item.id ===id && (todos[index].completed=!todos[index].completed));
? ? ?return [...todos]
? }
?
? ?default:
? ? ?return todos;
}
};
?
export default todos;
?
//刪除掉state文件。
?
//組件里:
寫法基本上不變
store.getState().todos
state數據不寫在組件內部訂閱,可以寫在主入口文件 訂閱store數據的更新
let render = ()=>{
? ?ReactDOM.render(
? ? ?
? ? ?document.getElementById('root')
? )
};
render();
store.subscribe(render);
react-redux
基于redux,專門為react使用redux而生,react-redux是連接redux和react組件的橋梁
react-redux做了哪些事情?
首先,看看redux里的問題:
1、組件中出現了大量的store對象
2、 在redux里,凡是使用state里數據的組件,必須加上 store.subscribe() 函數,否則,數據不是響應式的
react-redux的API
(react-redux僅有2個API)
?
import {Provider} from "react-redux";
import store from './redux/store'
?
ReactDOM.render((
? ?
? ? ? ?
? ?
), document.getElementById('root'));
connect(): 鏈接 ,(返回值)是個高階組件,用來鏈接react組件和redux(組件狀態(tài)要從redux中獲?。?/p>
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
功能:把store和react組件聯(lián)系在一起。只要store發(fā)生了變化就會調用mapStateToProps方法。Connect方法(的返回值)就是個高階組件。
參數1:mapStateToProps是個函數,
功能: 把倉庫里的state合并到當前組件里的props上。給mapStateToProps函數傳入所有state,它返回指定的state數據(需要合并到組件props中的state)。返回的state與組件的 props 合并(聯(lián)系的體現)。另外,當store發(fā)生變化時,mapStateToProps方法就會更新組件里的props,那么組件就更新了(因為props變了)。
參數:
state:所有的state
返回值:
指定的state(組件里需要的state)。
示例代碼:
const mapStateToProps = (state)=>{
? ? ?return {
? ? ? ? ? count:state.count
? ? }
}
參數2:mapDispatchToProps函數
功能:
把dispatch和props聯(lián)系起來。傳入dispatch,返回綁定好的action方法。
更改數據必須要觸發(fā)action,所以,mapDispatchToProps把 action 作為組件的props 進行綁定(聯(lián)系的體現),要派發(fā)的函數名,可以是多個函數。mapDispatchToProps 就是用于建立組件跟store.dispatch(是action)的映射關系。
參數:
dispatch: 派發(fā)
ownProps:當前組件的props,即使用標簽時,傳入的props
返回值:
對象:表示所有dispatch的對象
示例代碼:
import React from "react";
import './App.css';
import {ADD,REDUCE} from "./store/action";
import {connect} from "react-redux";
?
class App extends React.Component {
?
?add(){
? ?this.props.add();
}
?
?reduce(){
? ?this.props.reduce();
}
?
?render = () => {
? ? ?return (
? ? ? ? ?
? ? ? ? ? ?
{this.props.count}
? ? ? ? ? ?this.add()} />
? ? ? ? ? ?this.reduce()} /> ? ? ? ? ? ?
? ? ? ? ?
? ? ? )
? }
}
?
let mapStateToProps= state=>{
?return {
? ?count:state.count
}
}
?
let mapDispatchToProps = dispatch=> ({
?add: () => dispatch(ADD()),
?reduce: () => dispatch(REDUCE()) ?
})
?
export default connect(mapStateToProps,mapDispatchToProps)(App);
react-redux的思路:
1)、用Provider包裹最頂層的組件,提供一個store屬性。這樣在任何組件里都可以使用store了。
2)、使用connect()函數來鏈接react的組件和redux的store。記?。篶onnect不能單獨使用,必須要有Provider
最佳實現(完整的代碼)
安裝:npm install --save react-redux
?
//1、主入口文件 index.js
import {Provider} from 'react-redux'
import store from './plugins/redux'
?
?
?
//2、容器組件里:App組件
import {connect} from "react-redux";
class App extends React.Component {
?add(){
? ?//直接用props來調用dispatch,而不需要store
? ?this.props.dispatch({
? ? ?type:"INCREMENT",
? ? ?payload:2
? });
}
? ?
?render = () => (
?
? ? ? ?
{this.props.count}
// 使用props可以直接拿到state里的數據,而不需要store? ? ? ?this.add()} />
? ?
? ) ? ?
}
?
//容器組件對外開放時,(把redux里的state轉到props)
export default connect((state)=>{
?return {
? ?count :state.count ? ?
}
})(App);
在react-redux里,把組件進行拆分(容器組件和UI組件)
容器組件:處理業(yè)務邏輯,有狀態(tài)(在redux里存放)組件,也叫智能組件
UI組件:只做展示,就是無狀態(tài)組件,也叫木偶組件
ui組件 ant-design Ant-Design-Mobile element-ui (react)等
ant-design
immutable
Immutable.js 介紹
引用類型的變量的優(yōu)點是節(jié)約內存,我們稱這樣的方式是Mutable(可變的)。但是當一個項目越來越復雜的時候,Mutable帶來的內存優(yōu)勢,消失殆盡。雖然我們可以進行deepCopy(深拷貝),但是這樣會造成CPU和內存的浪費。Immutable就是來解決這樣的問題的。
Immutable( 不可改變的 ) Data 就是一旦創(chuàng)建,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象。Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據創(chuàng)建新數據時,要保證舊數據同時可用且不變。同時為了避免 deepCopy 把所有節(jié)點都復制一遍帶來的性能損耗,Immutable 使用了Structural Sharing(結構共享),即如果對象樹中一個節(jié)點發(fā)生變化,只修改這個節(jié)點和受它影響的父節(jié)點,其它節(jié)點則進行共享 。
(1)Immutable優(yōu)點:
減少內存的使用(深拷貝消耗內存)
并發(fā)安全
降低項目的復雜度
(2)Immutable缺點:
庫的大?。ńㄗh使用seamless-immutable)
對現有項目入侵嚴重
容易與原生的對象進行混淆
深拷貝與淺拷貝的關系
面試題:深拷貝和淺拷貝(超級詳細,有內存圖)
面試題:深拷貝和淺拷貝(超級詳細,有內存圖)_深copy和淺copy面試-CSDN博客
Immutable 中常用類型(Map,List)
Map()
作用:復制一個對象(鍵值對)
參數:json對象
返回值:Map對象(經過immutable包裝了的Map對象) ,該Map對象也可以使用set,get方法。但是set方法調用后會產生一個新的Map對象
Immutable.Map(json對象);
示例:
npm i --save immutable
?
function f(){
? ?//定義一個對象obj1
? ?var obj1 = {
?? ??? ?id:"007",
?? ??? ?name:"張三瘋",
? ? ? ?address:{
? ? ? ? ? ?province:"陜西省",
? ? ? ? ? ?city:"西安市"
? ? ? }
?? ?};
? ?//淺復制:let obj2 = obj1; 兩個對象會共享同一塊內存區(qū)域
? ?//深拷貝:兩個對象的內存是獨立的,
? ?
? ?//使用Immutable.Map()進行復制,相同的數據會共享。
? ?let obj2 = Immutable.map(obj1).toJS();
? ?//修改數據時,只把改動數據及其父級數據部分進行復制,其它部分不做復制,依然共享,節(jié)約了內存。
? ?obj1.address.province="北京";
?? ?console.log(obj1);//obj1.address.province是北京
?? ?console.log(obj2);//obj2.address.province是陜西
}
Map對象的set函數會產生一個新的對象
示例:
?
function f(){
?var obj1 = {
?? ??? ?id:"007",
?? ??? ?name:"張三瘋"
?? ?};
?
?let obj2 = Immutable.Map(obj1); ?
? ?
?let obj3 = obj2.set("name","李思峰"); ?
?let obj4 = obj2.set("sex","男");
?
?console.log(obj2.toJS()); ? //{id: "007", name: "張三瘋"}
?console.log(obj3.toJS()); ? ?//{id: "007", name: "李思峰"}
?console.log(obj4.toJS()); ? ?//{id: "007", name: "張三瘋", sex: "男"}
}
List
可重復的有序列表。對應原生JS的 Array
作用:復制一個數組
參數:數組
返回值:List。
是有序的索引密集集合,類似于JavaScript數組,針對List類型有set和get方法,分別表示設置和獲取
function f(){
? ?
?const persons = ['芙蓉姐姐','春哥','犀利哥']
?let ipersons = Immutable.List(persons);
?
?let ipersons2 = ipersons.set( 1, '干露露');
?
?console.log("ipersons",ipersons);//List對象
?console.log("ipersons.toJS()",ipersons.toJS())
?
?console.log("ipersons2",ipersons2);//List對象
?console.log("ipersons2.toJS()",ipersons2.toJS())
}
Immutable 中常用方法(fromJS,toJS,is())
// (1) Immutable.fromJs(),把一個js對象(數組)轉化為Immutable對象。
?
? ?let map = Immutable.fromJS({ a: 1,b: 1,c: 1 });
? ?console.log(map);
?
? ?let list = Immutable.fromJS(['芙蓉姐姐','春哥','犀利哥']);
? ? console.log(list);
?
// (2) immutable對象.toJS(),把一個Immutable對象轉化為js對象(數組)。
let o = ?map.toJS();
? ? console.log(o);
let arr2 = list.toJS();
? ? ?console.log(arr2);
?
// (3) Immutable.is():比較兩個Map(或List)的值是否相等
? var map1 = Immutable.Map({ a: 1,b: 1,c: 1 });
? var map2 = Immutable.Map({ a: 1,b: 1,c: 1 });
? console.log(Immutable.is(map1, map2));//true
Immutable + Redux 的開發(fā)方式
如果我們希望,redux中的state也是用immutable的方式。那就需要使用redux-immutable提供的combineReducers來代替。
//創(chuàng)建倉庫
?
import {combineReducers} from 'redux-immutable';
import { createStore } from "redux";
?
import reducer from "../store/reducer";
?
?
let rootReducers = combineReducers({reducer});
?
let store = createStore(rootReducers);
?
export default store;
reducers中的state也需要使用immutable
//reducer
?
// 初始化reducer中的initialState時建議用List方法而不是fromJS方法,效率更高
import Immutable from "immutable";
?
let initState=Immutable.List([
? {
? ? ? ?text:"吃飯",
? ? ? ?isComplete:false
? },
? {
? ? ? ?text:"睡覺",
? ? ? ?isComplete:true
? }
])
?
export default (state=initState,action)=>{
? ?//let state1 = state.toJS();
? ?let {type,payload} = action;
? ?switch(type){
? ? ? ?case "ADDTODO":{
? ? ? ? ? ?let obj = {
? ? ? ? ? ? ? ?text:payload,
? ? ? ? ? ? ? ?isComplete:false
? ? ? ? ? }
? ? ? ? ? ?return state.set(state.size,obj]);
? ? ? }
? ? ? ?default:return state;
? }
}
組件里面,獲取到數據后,就需要使用toJS()進行轉換。或者使用immutable的函數進行操作。
組件:
class App extends React.Component {
?constructor(props){
? ?super(props);
? ?console.log("store.getState()",store.getState().toJS().reducer);
? ?this.state ={
? ? ? ?todos:store.getState().toJS().reducer,
? ? ? ?text:""
? }
}
?
?componentDidMount(){
? ?store.subscribe(()=>{
? ? ?this.setState({
? ? ? ?todos:store.getState().toJS().reducer
? ? })
? });
}
…………………………
mobx(視情況介紹一下就行)
1.1. MobX 要點 · MobX 中文文檔
介紹
一款可以與redux媲美的數據流方案。Flux思想單向數據流方案,以 Redux 為代表;Reactive響應式數據流方案,以 Mobx 為代表;
單向數據流實現:redux + react-redux + react-thunk 響應式數據流實現:mobx + mobx-react
MobX 的理念是通過觀察者模式對數據做出追蹤處理,在對可觀察屬性作出變更或者引用的時候,觸發(fā)其依賴的監(jiān)聽函數,整體的store注入機制采用react提供的context來進行傳遞
適用場景可以是react vue angular mpvue 小程序 taro
裝飾器Decorator
是個函數,用來裝飾類或者類成員 ,是Object.defineProperty的語法糖
//給對象添加或修改屬性
Object.defineProperty(target, prop, desc)
//target 需要定義屬性的當前對象
//prop 當前需要定義的屬性名 類型:字符
desc默認值說明configurablefalse描述屬性是否可以被刪除,默認為 falseenumerablefalse描述屬性是否可以被for...in或Object.keys枚舉,默認為 falsewritablefalse描述屬性是否可以修改,默認為 falsegetundefined當訪問屬性時觸發(fā)該方法,默認為undefinedsetundefined當屬性被修改時觸發(fā)該方法,默認為undefinedvalueundefined屬性值,默認為undefined
//定義裝飾器(以下代碼會被轉成Object.defineProperty的寫法)
function 裝飾器名 (target,prop,descriptor){
?descriptor.writable=false;//writable屬性是否可以寫入
}
?
//使用裝飾器
@裝飾器名 類
@裝飾器名 實例屬性|靜態(tài)屬性(類的屬性)
@裝飾器名 實例方法|靜態(tài)方法(類的方式)
?
//使用場景
mobx / angluarTs / vueTs / reactTs / java ...
?
配置
create-react-app腳手架 不支持裝飾器語法,需要小配一下
yarn add @babel/plugin-proposal-decorators --save
package.json
babel: {
?"presets":...
?
?+
?"plugins": [
? ? ["@babel/plugin-proposal-decorators", { "legacy": true }],
? ]
?
?....
}
vscode編輯配置
去掉vscode中使用裝飾器寫法時的,波浪線提示
vscode->文件->首選項-->設置->搜索:experimentalDecorators->勾上 ?
//webstrom 無需設置
如果提示:
Parsing error: Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.
?
請安裝:
npm install --save-dev babel-plugin-transform-decorators-legacy
?
模塊的版本:
"@babel/core": "7.12.3",
"@babel/plugin-proposal-decorators": "^7.12.12",
"@pmmmwh/react-refresh-webpack-plugin": "0.4.2",
"@svgr/webpack": "5.4.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
"antd": "^4.10.0",
"axios": "^0.21.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.0",
"babel-loader": "8.1.0",
"babel-plugin-named-asset-import": "^0.3.7",
"babel-preset-react-app": "^10.0.0",
"bfj": "^7.0.2",
"camelcase": "^6.1.0",
"case-sensitive-paths-webpack-plugin": "2.3.0",
"css-loader": "4.3.0",
"dotenv": "8.2.0",
"dotenv-expand": "5.1.0",
"echarts": "^5.0.0",
"eslint": "^7.11.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.9.2",
"eslint-webpack-plugin": "^2.1.0",
"file-loader": "6.1.1",
"fs-extra": "^9.0.1",
"html-webpack-plugin": "4.5.0",
"http-proxy-middleware": "^1.0.6",
"identity-obj-proxy": "3.0.0",
"immutable": "^4.0.0-rc.12",
"jest": "26.6.0",
"jest-circus": "26.6.0",
"jest-resolve": "26.6.0",
"jest-watch-typeahead": "0.6.1",
"mini-css-extract-plugin": "0.11.3",
"mobx": "^6.0.4",
"mobx-react": "^7.0.5",
"optimize-css-assets-webpack-plugin": "5.0.4",
"pnp-webpack-plugin": "1.6.4",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-loader": "3.0.0",
"postcss-normalize": "8.0.1",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "5.0.2",
"prompts": "2.4.0",
"react": "^17.0.1",
"react-app-polyfill": "^2.0.0",
"react-dev-utils": "^11.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
"react-refresh": "^0.8.3",
"react-router-dom": "^5.2.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"resolve": "1.18.1",
"resolve-url-loader": "^3.1.2",
"sass-loader": "8.0.2",
"semver": "7.3.2",
"style-loader": "1.3.0",
"terser-webpack-plugin": "4.2.3",
"ts-pnp": "1.2.0",
"typescript": "^4.1.3",
"url-loader": "4.1.1",
"web-vitals": "^0.2.4",
"webpack": "4.44.2",
"webpack-dev-server": "3.11.0",
"webpack-manifest-plugin": "2.2.0",
"workbox-webpack-plugin": "5.1.4"
示例:
定義一個只讀裝飾器(其實就是個函數),設定屬性是只讀的。
function ?readonly(target,prop,desc) {
?desc.writable = false;
}
?
class Dog{
?//使用裝飾器,修飾dark屬性為只讀的
? @readonly dark="wangwang";
}
?
let d = ?new Dog();
d.dark="mie"; //這個是不能修改成功的
console.log(d.dark);
mobx成員
observable 裝飾類和其成員
@observable 裝飾store類的成員,讓store中的成員成為被觀察者,即:在組件里可以訂閱該數據的變化。
Mbox的思想是:把state和reducer都放在一個類中。state就是數據,reducer就是方法。
示例:創(chuàng)建倉庫
./src/mobxstore/index.js
? ?
import { observable } from 'mobx';
?
//1)、state:
//(在對象外面套上observable()函數,就可以,讓對象里的數據可以被訂閱)
let appStore = observable({
? ?count:0
});
?
//2)、reducer:
appStore.increment = function () {
? ?console.log("increment");
? ?appStore.count += 1;
}
?
appStore.decrement = function () {
? ?console.log("increment");
? ?appStore.count -= 1;
}
?
export default appStore;
mobx-react成員
Provider inject observer
Provider,頂層提供appStore的服務
?
inject:在組件里使用倉庫對象(appStore)。 Provider和inject結合起來,就可以讓頂層提供的倉庫對象(appStore)在組件里(props)使用
@inject('appStore')
class Index extend React.Component{
? ? render(){
? ? ? ?
? ? ? ? {this.props.appStore.屬性名}
? ? ? ?
? ? }
}
observer:能夠監(jiān)聽到數據的變化,并響應到組件里。即:在組件里訂閱了store中的數據,當store中的數據發(fā)生變化時,就會發(fā)布給組件。
@observer
class Index extend React.Component{
? ? render(){
? ? ? ?
? ? ? ? {this.props.appStore.屬性名}
? ? ? ?
? ? }
}
Mobx 的使用
1、安裝:npm install mobx --save 或者 yarn add mobx 2、創(chuàng)建倉庫
文件名: ./mobxstore/index.js
?
import { observable } from 'mobx';
//1)、定義狀態(tài)并使其可觀察
//state:(在對象外面套上observable()函數,就可以,讓對象里的數據可以被訂閱)
?
const appStore = observable({
? ?count: 0
});
?
appStore.increment = function () {
? ?console.log("increment");
? ?appStore.count += 1;
}
?
export default appStore;
3、入口文件:
在頂層提供 倉庫對象(appStore)
//index.js
?
import appStore from './mobxstore';
import { Provider } from "mobx-react";
?
ReactDOM.render(
?
? ?
? ? ?
? ?
?,
?document.getElementById('root')
);
4、使用數據的組件
需要使用 observer,inject
?
import { Component } from "react";
import {observer,inject} from "mobx-react";
?
@inject('appStore') ?// 使用appStore對象,對應著頂層組件的Provider提供的appStore,同時,把appStore映射到當前組件的props上了
@observer ? //監(jiān)聽appStore中數據的變化,即:只要appStore中的數據發(fā)生變化,就會重新渲染該組件
class Index extends Component { ? ?
? ?//這個不能用箭頭函數 ? ?
? ?render(){
? ? ? ?return(
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? count:{this.props.appStore.count} ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?
? ? ? ? ? )
? }
}
?
export default Index;
?
5、修改數據的組件:
需要使用 inject
import {observer,inject} from "mobx-react";
?
@inject('appStore') ?// 使用appStore對象,對應著頂層組件的Provider提供的appStore
export default class Layout extends Component {
?
? ?incCount(){
? ? ? ?this.props.appStore.increment();
? }
? ?
? ?render = () => (
? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ?
? )
}
React17
React 17版本不同尋常,因為它沒有添加任何面向開發(fā)人員的新功能。取而代之的是,該發(fā)行版主要致力于簡化React本身的升級。
生命周期
react17 ,不建議使用componentWillMount,componentWillUpdate,這些生命周期鉤子函數,因為,這些鉤子函數函數有安全問題
事件代理更改
在React 16和更早的版本中,React將對大多數事件代理執(zhí)行document.addEventListener()。 React 17將在后調用rootNode.addEventListener()。
………………
滑動驗證頁面
react+typescript
搭建項目(支持typescript):
create-react-app 項目名稱 --typescript
不同
文件擴展名:js變成了ts,jsx變成了tsx
代碼示例:
1)、類組件里使用ts
//類組件里使用ts
?
import React from "react";
?
interface IProps {
? ?name:string,
? ?age?:number
}
?
interface IState {
? ?color:string
}
?
export default class Home extends React.Component
? ?constructor(props:IProps){
? ? ? ?super(props);
? ? ? ?this.state = {
? ? ? ? ? ?color:"red"
? ? ? }
? }
?
? ?changeColor=()=>{
? ? ? ?this.setState({
? ? ? ? ? ?color:"blue"
? ? ? });
? }
?
? ?render=()=>{
? ? ? ?return (
? ? ? ?
? ? ? ? ? ?
姓名:{this.props.name}
? ? ? ? ? ?
姓名:{this.props.age || 0}
? ? ? ? ? ?
? ? ? ?
? ? ? )
? }
}
2)、函數式組件
//Counter組件代碼:
?
import React from "react";
?
interface IProps {
? ?count:number,
? ?increment:()=>void
}
const Counter=(props:IProps) =>{
//const Counter=({count,increment}:IProps) =>{ ? ? ?
? ?return(
? ? ? ?<>
? ? ? ? ? ?
{props.count}
? ? ? ? ? ?
? ? ? ?>
? )
}
?
export default Counter;
?
?
App組件代碼:
?
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './pages/Home/Home';
import Counter from './pages/Counter/Counter';
?
interface IProps {
}
?
interface IState {
?count:number
}
?
export default class App extends React.Component
?constructor(props:IProps){
? ? ?super(props);
? ? ?this.state = {
? ? ? ? ?count:0
? ? }
}
?
?increment=()=>{
? ?this.setState({
? ? ?count:this.state.count+1
? });
}
?
?render=()=>{
? ?return (
? ? ?
? ? ? ? ?
? ? ? ? ?
? ? ?
? ? );
? }
}
高階組件:
import React from 'react';
?
class News extends React.Component{
?
?render=()=>{
? ?return (
? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ?
我是新聞,我拍誰
? ? ? ? ? ? ? ? ?
我是新聞,我拍誰
? ? ? ? ? ? ? ? ?
我是新聞,我拍誰
? ? ? ? ? ? ?
? ? ? ? ? );
? }
}
?
//高階組件
const withCopyRight = (WrappedCom:React.ComponentType)=>{
? ?return class extends React.Component{
? ? ? ?render=()=>(
? ? ? ? ? ? ? ?<>
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? @copyright:版權所有 2002A
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?>
? ? ? ? ? );
? }
}
?
export default withCopyRight(News);
事件處理,傳遞事件對象(React.mouseEvent)
定義 Event組件
?
import React, { ReactNode } from "react";
?
interface IProps{
? ?name:string,
? ?click:(e:React.MouseEvent)=>void
}
?
export default class Event extends React.Component
?
? ?render=()=>{
? ? ?return (
? ? ? ?
? ? ? ? ? ?
? ? ? ?
? ? );
? ? }
}
?
//引用Event組件
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Event from './pages/Event/Event';
?
interface IProps {
}
?
?
export default class App extends React.Component
?constructor(props:IProps){
? ? ?super(props);
? }
?
?
?handClick=(e:React.MouseEvent)=>{
? ?console.log(e);
? ?console.log(e.target);
}
?
?render=()=>{
? ?return (
? ? ?
? ?
? ? ? ? ?
? ? ?
? );
}
}
// export default App; ?
umi
官網 項目
介紹:
UmiJS - 插件化的企業(yè)級前端應用框架
Umi,中文可發(fā)音為烏米,是可擴展的企業(yè)級前端應用框架。Umi 以路由為基礎的,同時支持配置式路由和約定式路由,保證路由的功能完備,并以此進行功能擴展。然后配以生命周期完善的插件體系,覆蓋從源碼到構建產物的每個生命周期,支持各種功能擴展和業(yè)務需求。
Umi 是螞蟻金服的底層前端框架,已直接或間接地服務了 3000+ 應用,包括 java、node、H5 無線、離線(Hybrid)應用、純前端 assets 應用、CMS 應用等。他已經很好地服務了我們的內部用戶,同時希望他也能服務好外部用戶。
安裝umi
1)、安裝:
yarn add global umi@2.12.9 (react 整理推薦用yarn ? 瑕疵node-sass)
或
?
npm i -g umi@2.12.9
2)、驗證
umi -v
順便補充:yarn和npm 全局安裝的文件在哪兒呢?
yarn全局位置: yarn global bin
npm全局位置:npm config list
手工(命令)搭建項目
創(chuàng)建項目目錄
mkdir demoprj01
創(chuàng)建頁面
//命令的方式創(chuàng)建頁面。
?
//創(chuàng)建組件index
umi g page index
//創(chuàng)建組件users
umi g page users
啟動項目
umi dev
用命令umi dev啟動項目 后,大家會發(fā)現 項目目錄 下多了個 .umi 的目錄。這是啥?這是 umi 的臨時目錄,可以在這里做一些驗證,但請不要直接在這里修改代碼,umi 重啟或者 pages 下的文件修改都會重新生成這個文件夾下的文件。
這個文件夾里有自動生成的路由配置(.umi/core/routes.ts)
約定式路由
umi 里約定默認情況下 pages 下所有的 js 文件即路由 。
https://github.com/umijs/umi/blob/umi%402.12.9/packages/umi/src/link.js
1)、直接使用react-router-dom
?
import {Link} from 'react-router-dom';
?
go to /users
?
2)、使用umi的link模塊
?
? ?在umi的腳手架里使用
?
腳手架創(chuàng)建項目
創(chuàng)建項目目錄:mkdir project
轉換項目目錄:cd project
使用yarn產生umi的項目:yarn create umi
安裝所有的依賴:yarn install
運行項目:yarn start
項目結構
|-public 本地數據資源
|-mock umi支持mock數據,無需代理
|-src
?|-assets 開發(fā)資源
?|-components 通用組件
?|-layouts 為根布局,根據不同的路由選擇return不同的布局,可以嵌套一些和布局相關的組件
?|-pages 頁面 約定式路由的頁面
?|-global.css 全局樣式
|-umirc 編譯時配置
路由
約定式路由
umi里使用的約定式(固定規(guī)則)路由。 規(guī)則如下:
|-pages
?|-index.js ? "/" 路由 頁面
?|-index/index.js "/" 路由 頁面
?
?|-goods.js ?// /goods 路由
?|-goods/index.js ?// /goods 路由頁
? ?//goods 路由的默認頁 return null 代表沒有默認頁
?
?|-goods/$id.js ?// /goods/1 路由頁
?|-goods/$id$.js ?// /goods 路由。 goods下沒有index 時 展示了 /goods 或 /goods/2 代表id可選
?|-goods/_layout.js ?// /goods 路由頁 有自己的展示區(qū) {props.children} 不然會在父的展示區(qū)展示
?
?|-goods/category.js ? // /goods/category 路由
?|-goods/category/index.js // /goods/category 路由
?
?|-404.js 生產模式下的404頁,開發(fā)模式默認umi的
注意:
一定要關閉默認路由配置,否則,目錄自動生成路由(約定式路由)失效,
如何關閉:修改.umirc.js文件:注釋routes鍵數據即可
當在地址欄輸入: http://localhost:8000/ 時,
會找layouts/index.js組件(布局)。在layouts/index.js里的{props.children}里會顯示pages/index.js
路由跳轉
聲明式
商品 002
編程式
import router from 'umi/router';
router.push('/login')
?
router.push({
?pathname:'/goods/1',
?query:{a:11,b:22},
?search:'a=111&b=222'
})
// search:'a=111&b=222' 如果傳遞了search 會覆蓋query,query只是對search的封裝引用了search的值
?
props.history.xx() //可用
傳接參
props.match.params
props.location.query 返回對象
案例需求
基礎路由 - 練習:創(chuàng)建文章模塊列表、添加
動態(tài)路由 - 練習:文章查看詳情
嵌套路由 - 練習:面包屑(列表、添加、查看詳情都要)
全局layout - 練習:后臺三欄布局(頭部、左側、右側(內容))
404路由 - 練習:搞404(留心坑) 、不同的全局 layout
實現步驟
1、創(chuàng)建文章模塊列表、添加(基礎路由 )
1)、列表頁面
umi g page arts/index
運行:yarn start
2)、添加頁面
umi g page arts/create
運行:yarn start
2、文章詳情(動態(tài)路由)
umi g page 'arts/$id' --less
在產生的$id.js文件里,寫如下代碼:
Page {props.match.params.id}
//獲取路由上的id的值運行:yarn start,
在地址欄輸入 localhost:8000/arts/0001
3、面包屑 (嵌套路由)
后臺項目文章模塊,列表、添加詳情都需要面包屑 ,我們做一個公共的父組件 _layout
普及:公共的父組件不管是在后端、還是前端專業(yè)術語叫l(wèi)ayout
語法命令:umi g page arts/_layout --less
在arts下創(chuàng)建的文件 _layout.js,如果訪問,http://localhost:8000/arts 開頭的路徑,都會默認找 arts文件夾下的 _layout 組件。所以,在該組件里配置子路由,子路由對應的頁面顯示在{props.children}里。
在產生的_layout.js文件里,修改代碼:
export default function(props) {
return (
?
? ?
Page _layout
? ?
?
);
}
在地址欄輸入: http://localhost:8000/arts/
那么默認,會找arts文件夾下_layout.js文件,在
如果在地址輸入: http://localhost:8000/arts/01001
會找arts下_layout.js文件,在
4、后臺管理系統(tǒng)常見的三欄布局(頭部,下(左邊導航,右邊頁面))
1)、總體布局
//src/index.js
import styles from './index.less';
?
function BasicLayout(props) {
?return (
? ?
? ? ?
? ? ?
? ? ? ?
? ? ? ?
? ? ? ? {props.children}
? ? ? ?
? ? ?
? ?
);
}
?
export default BasicLayout;
src/layout/index.less
?
.normal {
?width: 100%;
?height: 100%;
?display: flex;
?flex-direction: column;
?
?.header{
? ?width: 100%;
? ?height: 120px;
? ?background-color: skyblue;
}
?
?.main{
? ?flex: 1;
? ?width: 100%;
? ?background-color: red;
? ?display: flex;
?
? ?.left{
? ? ?width: 200px;
? ? ?height: 100%;
? ? ?background-color: blue;
? }
? ?
? ?.right{
? ? ?flex: 1;
? ? ?height: 100%;
? ? ?background-color: #ccc;
? }
}
}
5、404 頁面
約定 pages/404.js 為 404 頁面,需返回 React 組件。
創(chuàng)建404頁面
umi g page 404 --less // 這個不行,404不能產生,因為,在命令行里要求文件名不能以數字開頭。
比如:
export default () => {
?return (
? ?
);
};
注意:開發(fā)模式下,umi 會添加一個默認的 404 頁面來輔助開發(fā),但你仍然可通過精確地訪問 /404 來驗證 404 頁面。上線后就不會這樣了。
另外,需要增加判斷,才能讓整個頁面都顯示404頁面。
修改 src/layout/index.js文件的內容
function BasicLayout(props) {
?if(props.location.pathname==="/404"){
? ?return props.children
}
?return (
? ?
? ? ?
? ? ?
? ? ? ?
? ? ? ?
? ? ? ? {props.children}
? ? ? ?
? ? ?
? ?
);
}
dva
官網:
介紹 | DvaJS
擴展
契合antd的富文本編輯器braft-editor
vue和react的區(qū)別
reactJsvueJs控制器--過濾器-√指令-√模板語法-√服務--組件√√jsx√2.0之后加入響應式原理必須調用setState函數真正的響應式
柚子快報邀請碼778899分享:前端 React 學習筆記
參考鏈接
本文內容根據網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。