一 : 腳手架
react官方提供的腳手架工具:Create-react-app
優(yōu)點:
- 官方提供窄坦,比較健壯与境;
- 使用簡單,可定制性強骤竹;
- 調(diào)試代碼方便蛾派;
如果沒有深厚webpack配置功底俄认,能夠確保駕馭更復雜的腳手架工具的話,使用這個腳手架是一個最佳選擇
腳手架的代碼不能直接運行洪乍,需要腳手架工具編譯才可以在瀏覽器運行眯杏,grunt/webpack等工具來幫助寫腳手架
Create React App:
npm install -g create-react-app
create-react-app my-app
cd my-app
npm start
生成文件:
my-app
- README.md 項目說明文件,支持markdown語法
- package.json 任何一個腳手架工具壳澳,都會有岂贩,代表node包文件
- .gitinore 使用git管理代碼的時候,一些文件不想傳到git倉庫上巷波,路徑寫下來
- node_modules 這個項目一些第三方依賴的包萎津,腳手架使用時需要的一些第三方依賴
- public
- favicon.ico 網(wǎng)頁標題icon
- index.html
- manifest.json 把網(wǎng)頁當成APP使用,保存快捷圖標時的配置
- src 項目源代碼
- index.js 入口
- App.test.js 自動化測試文件
package.json
{
"name": "my-app", //項目名稱
"version": "0.1.0", //版本號
"private": true, //私有項目
"dependencies": { //安裝的第三方依賴
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-scripts": "2.1.8"
},
"scripts": { //提供指令調(diào)用 npm run start啟動
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
serviceWorker的作用:
PWA: progressive web application
通過寫網(wǎng)頁的形式褥紫,來寫手機app應(yīng)用姜性,引入serviceWorker,借助網(wǎng)頁來寫手機APP應(yīng)用的功能
效果:寫了一個網(wǎng)頁髓考,并上傳到支持https協(xié)議的服務(wù)器上,這個網(wǎng)頁會具備這樣的特性(當用戶第一次訪問這個網(wǎng)頁弃酌,需要聯(lián)網(wǎng)才能看到氨菇,下次斷網(wǎng)的情況下,依然可以看到上次訪問過的頁面)serviceWorker會把之前瀏覽過的頁面妓湘,存儲在瀏覽器緩存中查蓉。
src/index.js
import * as serviceWorker from './serviceWorker'; 引用
serviceWorker.unregister(); 調(diào)用
把網(wǎng)頁當成APP顯示時的配置
手機/電腦 通過快捷方式,直接進入網(wǎng)址
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [ 快捷方式圖標
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".", 跳轉(zhuǎn)網(wǎng)址
"display": "standalone",
"theme_color": "#000000", 主題顏色
"background_color": "#ffffff"
}
二:組件
src/index.js
import App from './App';
加載一個App的文件榜贴,這個App 就是一個小的組件
App來自App.js文件(src/App.js)
---------------------
src/App.js
import React, { Component } from 'react';
定義一個類App豌研,繼承了React.Component的類
當一個類繼承了React.Component的類時候妹田,它就是一個組件了
class App extends Component {
render() {
return (
<div>
hello world!
</div>
);
}
render函數(shù)返回什么,這個組件就展示什么內(nèi)容
}
export default App; 導出
{ Component } 等價于 React.Component
import { Component } from 'react';
等價于
import React from 'react';
const Component = React.Component
--------------------
src/index.js
import App from './App'; 引入組件
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render() 在做什么:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM 第三方模塊
ReactDOM有一個方法render鹃共,可以把一個組件掛載到一個dom節(jié)點上
<App /> 是jsx語法鬼佣,如果使用了jsx語法,
就一定要在項目中引入import React from 'react'語法
組件名首字母必須大寫
import app from './App';
ReactDOM.render(<app />, document.getElementById('root'));
這種寫法會報錯,jsx不支持
在JSX語法中霜浴,如果要使用自己創(chuàng)建的組件晶衷,組件開頭必須用大寫字母開頭
三:最基礎(chǔ)的JSX語法
src/App.js
import React, { Component } from 'react';
class App extends Component {
render() {
// JSX
return (
<div>
hello, 阿魯提爾
</div>
);
}
}
export default App;
在react中,在js寫html標簽阴孟,就是JSX語法
<App /> 自定義組件
<div> 小寫開頭晌纫,h5原始標簽
四:使用React編寫Todolist功能
src/TodoList.js
import React, { Component,Fragment } from 'react';
class Todolist extends Component {
//在react中定義數(shù)據(jù)
// 在js中,一個類就一定會有一個constructor構(gòu)造函數(shù)
// 當創(chuàng)建一個Todolist實例永丝,constructor這個函數(shù)是優(yōu)于其他任何函數(shù)
// 最先被執(zhí)行的函數(shù)
// 會接受一個props參數(shù)
constructor(props){
super(props)
//Todolist 繼承了React.Component組件锹漱,所以在創(chuàng)建Todolist時,super是調(diào)用了Component的構(gòu)造函數(shù)
//調(diào)用一次
//定義數(shù)據(jù)
//數(shù)據(jù)定義在狀態(tài)里面慕嚷,this.state就是Todolist的狀態(tài)
//state負責存儲組件里面的數(shù)據(jù)
this.state = {
inputValue:'',
list:['學習英文','學習React']
}
}
render() {
return (
<Fragment>
<div>
<input
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return (
<li
onClick={this.handleItemDelete.bind(this,index)}
key={index}
>
{item}
</li>
)
})
}
</ul>
</Fragment>
)
}
handleInputChange(e){
//改變數(shù)據(jù)凌蔬,向里面?zhèn)魅雽ο? this.setState({
inputValue: e.target.value
})
// this.state.inputValue = e.target.value
}
handleBtnClick(){
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
})
}
handleItemDelete(index){
const list = [...this.state.list]
list.splice(index,1)
this.setState({
list,
})
錯誤寫法,刪除出bug,留下做個標記,一會慢慢研究
// let list = this.state.list.splice(index,1)
// this.setState({
// list,
// })
強烈不推薦錯誤寫法闯冷,違反書寫規(guī)定
// this.state.list.splice(index,1)
// this.setState({
// list:this.state.list
// })
//immutable
//state 不允許我們做任何的改變 所以賦值出來再修改
//一旦修改state里面的內(nèi)容砂心,后面會影響React性能優(yōu)化
}
}
export default Todolist;
//React規(guī)定,最外層必須包裹一個元素
// 如果想最外層包裹一個元素蛇耀,這個元素又不被顯示出來
//在react16版本里面辩诞,提供了一個Fragment占位符
五:JSX語法細節(jié)補充
0. JSX中的注釋:
{/* do something*/}
1. <input /> 中value和defaultValue的區(qū)別:
- 如果input 表單只是顯示默認值,不會手動去修改該值撩炊,則值需要使用defaultValue
- 如果需要獲取用戶輸入的值外永,則一定要用value屬性,但是一定要配合onchange事件一起使用拧咳,否則值是不會變的
2. 樣式className代替class(與類沖突)
<input className="input"
value={this.state.InputValue}
onChange={this.handleInputValue.bind(this)}
/>
3. dangerouslySetInnerHTML的作用:
在上面的TodoList中伯顶,input輸入<h1>學習node.js</h1>
,<h1>也會被當成文本顯示,dangerouslySetInnerHTML的作用就是骆膝,不顯示<h1>標簽祭衩,當做html添加到頁面
dangerouslySetInnerHTML 危險的設(shè)置元素innerHTML屬性
<li
key={index}
onClick={this.handleRemoveInputValue.bind(this,index)}
dangerouslySetInnerHTML = {{__html:item}}
>
{/* {item} 使用了dangerouslySetInnerHTML = {{__html:item}},這里item就不需要寫了 */}
</li>
//dangerouslySetInnerHTML = {{__html:item}}
4. for和htmlFor的區(qū)別
<label for="insertArea">輸入內(nèi)容</label>
<input
id="insertArea"
className="input"
value={this.state.InputValue}
onChange={this.handleInputValue.bind(this)}
onKeyDown={this.handleEnterInput.bind(this)}
/>
增加輸入框選擇區(qū)域的功能,雖然可以正常顯示阅签,但是會出現(xiàn)如下報錯
這是因為這個for會和循環(huán)的for產(chǎn)生歧義掐暮。要換成htmlFor
六:組件的拆分與組件之間的傳值
父組件向子組件傳值
src/TodoList.js
import TodoItem from './TodoItem';
<ul>
{
this.state.list.map((item,index)=>{
return (
<div>
<TodoItem content={item} /> //使用組件,通過屬性傳值
{/*<li //注釋內(nèi)容
key={index}
onClick={this.handleRemoveInputValue.bind(this,index)}
dangerouslySetInnerHTML = {{__html:item}}
>
</li>*/}
</div>
)
})
}
</ul>
src/TodoItem.js
import React,{Component} from 'react';
class TodoItem extends Component{
render(){
return <div>{this.props.content}</div>
{/*子組件通過this.props.content 獲取值*/}
}
}
export default TodoItem;
子組件向父組件傳值
src/TodoList.js
import React,{Component,Fragment} from 'react';
import './style.css'
import TodoItem from './TodoItem';
class TodoList extends Component{
constructor(props){
super(props)
this.state={
InputValue:'',
list:['學習英語','學習React']
}
}
render(){
return (
<Fragment>
<div>
<label htmlFor="insertArea">輸入內(nèi)容</label>
<input
id="insertArea"
className="input"
value={this.state.InputValue}
onChange={this.handleInputValue.bind(this)}
onKeyDown={this.handleEnterInput.bind(this)}
/>
<button onClick={this.handleUpInputValue.bind(this)}>提交</button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return (
<div>
<TodoItem
index={index}
content={item}
deleteItem = {this.handleRemoveInputValue.bind(this)}
{/*把函數(shù)傳給子組件政钟,并把this指向TodoList
把index和item通過屬性傳遞給TodoItem
在TodoItem組件中路克,使用
this.props.content
this.props.index
this.props.deleteItem
即可使用*/}
/>
</div>
)
})
}
</ul>
</Fragment>
)
}
handleInputValue(e){
this.setState({
InputValue: e.target.value
})
}
handleUpInputValue(){
this.setState({
list:[...this.state.list,this.state.InputValue],
InputValue:''
})
}
handleEnterInput(e){
if(e.keyCode=="13"&&!this.state.InputValue==''){
this.setState({
list:[...this.state.list,this.state.InputValue],
InputValue:''
})
}else if(e.keyCode=="13"&&this.state.InputValue==''){
console.log("不能為空")
}
}
handleRemoveInputValue(index){
const list = [...this.state.list]
list.splice(index,1)
this.setState({
list,
})
}
}
export default TodoList;
----------------------------------------------------
src/TodoItem.js
import React,{Component} from 'react';
class TodoItem extends Component{
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
render(){
return (
<div
onClick={this.handleClick}>
{this.props.content}
</div>)
}
handleClick(){
this.props.deleteItem(this.props.index)
}
}
export default TodoItem;
總結(jié): 父組件中(src/TodoList.js)
<TodoItem
index={index}
content={item}
deleteItem = {this.handleRemoveInputValue.bind(this)}
/>
把函數(shù)傳給子組件樟结,并把this指向TodoList
把index和item通過屬性傳遞給TodoItem
在TodoItem組件中,使用
this.props.content
this.props.index
this.props.deleteItem
即可使用
子組件中(src/TodoItem.js)
render(){
return (
<div
onClick={this.handleClick}>
{this.props.content} 綁定item
</div>)
}
handleClick(){
this.props.deleteItem(this.props.index)
調(diào)用父級函數(shù)精算,并把父級item傳遞進去
}
七:TodoList代碼優(yōu)化
src/TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
render() {
const {content} = this.props
return (
<li onClick={this.handleClick}>
{content}
</li>
)
}
handleClick(){
const { deleteItem,index } = this.props;
deleteItem(index)
}
}
export default TodoItem;
src/TodoList.js
import React, { Component,Fragment } from 'react';
import TodoItem from './TodoItem';
import './style.css';
class Todolist extends Component {
constructor(props){
super(props)
this.state = {
inputValue:'',
list:['學習英文','學習React']
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">輸入內(nèi)容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
getTodoItem(){
return this.state.list.map((item,index)=>{
return (
<TodoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
handleInputChange(e){
// 新版支持一個函數(shù)
// this.setState(()=>{
// return {
// inputValue: e.target.value
// }
// })
// ----------------
// ES6里面也可以去掉return,直接返回一個對象
// 有一個括號瓢宦,表示返回里面的對象
// this.setState(()=>({
// inputValue: e.target.value
// })
// )
//這樣寫報錯的原因,setState如果傳一個函數(shù)殖妇,是一個異步的刁笙,為了性能上的提升
//異步函數(shù) e.target.value會有一些問題
// ----------------
// 解決方法
const value = e.target.value //在外層保存一下
this.setState(()=>({
inputValue: value
}));
// ----------------
// 最初版
// this.setState({
// inputValue: e.target.value
// })
}
handleBtnClick(){
//prevState 指的是修改數(shù)據(jù)之前是什么樣的
//等價于this.state
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}));
}
handleItemDelete(index){
this.setState((prevState)=>{
const list = [...prevState.list];
list.splice(index,1)
return {list}
});
}
}
export default Todolist;
八:React高級內(nèi)容
React developer tools 安裝及使用插件:
google瀏覽器擴展程序 安裝React Developer Tools
安裝完成之后,控制臺會增加一個React菜單谦趣,可以查看頁面結(jié)構(gòu)和數(shù)據(jù)
PropTypes 與 DefaultProps 的應(yīng)用:
每個組件都有自己的prop參數(shù)疲吸,這個參數(shù)是從父組件接收的一些屬性。
- PropTypes的作用是在接受參數(shù)的時候前鹅,對參數(shù)類型做限制摘悴。
- DefaultProps的作用是定義參數(shù)的默認值。
src/TodoItem.js
import React, { Component } from 'react';
----------------
引入propTypes
import PropTypes from 'prop-types';
------------------
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
render() {
const {test,content} = this.props
return (
<li onClick={this.handleClick}>
{test} - {content}
</li>
)
}
handleClick(){
const { deleteItem,index } = this.props;
deleteItem(index)
}
}
--------------------------
使用
//組件名字TodoItem舰绘,對它的屬性做強校驗
TodoItem.propTypes = {
content: PropTypes.string, //content的類型必須是string
//PropTypes是上面引入的PropTypes
deleteItem: PropTypes.func,
index: PropTypes.number,
test: PropTypes.number test是沒傳的值蹂喻,不會做檢驗
如果要求必須傳test這個屬性可以這樣寫:
test: PropTypes.string.isRequired
也可以寫判斷
content: PropTypes.oneOfType([PropTypes.number,PropTypes.string])
數(shù)字或者字符串
}
有的時候必須要求傳遞test,但父組件確實沒辦法傳遞,可以給test定義一個默認值捂寿,來解決報錯問題
TodoItem.defaultProps = {
test: 'hello world'
}
--------------------------
export default TodoItem;
從父級傳遞來的屬性有:
content,deleteItem,index
PropTypes 不會阻止程序的正常運行口四,但是會在控制臺彈出一個報錯。
isRequired:表示必須要在父組件里傳遞秦陋,不傳遞會報警告
PropTypes的類型:
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: PropTypes.node,
// A React element (ie. <MyComponent />).
optionalElement: PropTypes.element,
optionalMessage: PropTypes.instanceOf(Message),
...
Typechecking With PropTypes - 文檔
props,state與render函數(shù)的關(guān)系:
- 當組件的state或者props發(fā)生改變的時候蔓彩,render函數(shù)就會重新執(zhí)行
- 當父組件的render函數(shù)被運行時,它的子組件的render都會被將被重新運行一次(可以從兩個方面理解驳概,1.自身的props變化赤嚼,重新運行;2.父組件重新渲染顺又,順帶把包含的子組件一起重新運行更卒。)
React中的 虛擬DOM:
深入了解虛擬DOM:
虛擬DOM中的Diff算法:
React中ref的使用:
在React中使用ref來操作DOM
src/TodoList.js
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">輸入內(nèi)容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input)=>{this.input = input}}
{/*構(gòu)建一個ref引用,這個引用叫this.input指向?qū)?yīng)的input dom節(jié)點*/}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
handleInputChange(e){
console.log(e.target)
//其實就是dom節(jié)點 <input id="insertArea" class="input" value="">
// const value = e.target.value
const value = this.input.value //e.target使用this.input來替換
this.setState(()=>({
inputValue: value
}));
}
不推薦使用ref稚照,原因:React中建議數(shù)據(jù)驅(qū)動的方式來編寫我們的代碼蹂空,盡量不要直接操作DOM
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">輸入內(nèi)容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input)=>{this.input = input}}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul ref={(ul)=>this.ul=ul}> {/*綁定ul*/}
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
handleBtnClick(){ //點擊按鈕
//prevState 指的是修改數(shù)據(jù)之前是什么樣的
//等價于this.state
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}));
console.log(this.ul.querySelectorAll("li").length);
//當點擊添加時,獲取的頁面數(shù)據(jù)長度總少一個锐锣,因為this.setState是異步的
正確辦法腌闯,使用this.setState第二個參數(shù),也是一個函數(shù)雕憔,當?shù)谝粋€函數(shù)被執(zhí)行成功的回調(diào)
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}),()=>{
console.log(this.ul.querySelectorAll("li").length);
});
}
九:React中的生命周期函數(shù)
生命周期函數(shù)指在某一個時刻組件會自動調(diào)用執(zhí)行的函數(shù)。
Initialization 是組件初始化
constructor(props) {
super(props);
this.state = {
inputValue:''
}
}
在這里會定義state,接收props
constructor是es6語法中自帶的函數(shù)糖声,所以不算React的生命周期函數(shù)斤彼。
但是和React生命周期函數(shù)沒有太大的區(qū)別
Mounting 掛載(組件第一次掛載到頁面上)
componentWillMount(){}
在組件即將被掛載到頁面的時刻自動執(zhí)行render(){}
組件渲染componentDidMount(){}
在組件被掛載到頁面之后分瘦,自動被執(zhí)行
componentWillMount和componentDidMount只會在組件第一次被放到頁面上的時候執(zhí)行(第一次掛載的時候)
Updation 組件更新的時候
- shouldComponentUpdate(){}
組件被更新之前,他會自動被執(zhí)行(當組件即將被變更的時候)
shouldComponentUpdate會要求返回一個布爾類型的結(jié)果
shouldComponentUpdate(){
return true
}
shouldComponentUpdate根據(jù)單次直譯:你的組件需要被更新嗎琉苇?
componentWillUpdate(){}
組件被更新之前嘲玫,它會自動執(zhí)行,但是他在shouldComponentUpdate之后執(zhí)行
如果shouldComponentUpdate返回true它才執(zhí)行
如果返回false并扇,這個函數(shù)就不會被執(zhí)行了render(){}
渲染組件componentDidUpdate(){}
組件更新完成之后去团,他會被執(zhí)行componentWillReceiveProps(){}
一個組件如果是頂層組件,沒有props傳遞進來穷蛹,是不會執(zhí)行的
當一個組件從父組件接受了參數(shù)
如果這個組件第一次存在于父組件中土陪,不會執(zhí)行
如果這個組件之前已經(jīng)存在于父組件中,才會執(zhí)行
(只要父組件的render函數(shù)被重新執(zhí)行了肴熏,子組件的這個生命周期函數(shù)就會被執(zhí)行)
Unmounting 組件從頁面去除
- componentWillUnmount(){}
當這個組件即將被從頁面中剔除的時候鬼雀,會被執(zhí)行
React developer tools插件使用:
對于一個組件,render函數(shù)被執(zhí)行有兩種情況:
- 在props和state發(fā)生變化的時候子組件會被渲染鸦做;
- 父組件render函數(shù)執(zhí)行
input輸入內(nèi)容時励烦,子組件被重復渲染在于第二種情況:這個邏輯沒問題,但會帶來性能上的損耗
父組件input框內(nèi)容發(fā)生變化泼诱,子組件沒有必要進行重新渲染坛掠,現(xiàn)在的機制會造成子組件進行很多無謂的渲染。
性能優(yōu)化:
src/TodoItem.js 子組件使用shouldComponentUpdate
shouldComponentUpdate(){
return false;
}
{/*shouldComponentUpdate 我的子組件被渲染一次之后坷檩,如果子組件需要被更新却音,那么強制要求不更新*/}
上面寫法不是最優(yōu)寫法
src/TodoItem.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
//React提升代碼性能一:把this作用域的修改放到constructor里面來做矢炼,可以保證整個程序里面作用域的綁定只會執(zhí)行一次系瓢,而且可以避免組件一些無謂的渲染
}
// React提升代碼性能二:React底層setState內(nèi)置了性能機制,是異步的函數(shù)句灌,可以把多次數(shù)據(jù)的改變結(jié)合成一次來做夷陋,降低虛擬DOM的使用頻率
//React提升代碼性能三:React底層用了虛擬DOM這個概念,還有同層比對胰锌,配值概念骗绕,來提升虛擬DOM比對速度,來提升React性能
shouldComponentUpdate(nextProps,nextState){
//React提升代碼性能四:當我的組件要被更新的時候资昧,props會被更新成什么
// nextProps 指接下來props會被更新成什么樣
// nextState 指接下來state會被更新成什么樣
if(nextProps.content !== this.props.content){
//如果接下來變化的content不等于當前props.content酬土,說明組件接收的content值發(fā)生了變化
//需要讓這個組件重新渲染
return true;
}else{
//沒有變化 返回false,組件沒有被重新渲染的必要
return false
}
}
render() {
console.log("child render")
const {content} = this.props
return (
<li onClick={this.handleClick}>
{content}
</li>
)
}
handleClick(){
const { deleteItem,index } = this.props;
deleteItem(index)
}
}
TodoItem.propTypes = {
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number,
}
export default TodoItem;
通過shouldComponentUpdate生命周期提升了組件性能,避免一個組件做無謂的render
render函數(shù)從新執(zhí)行就意味著React底層需要對組件生成一份虛擬DOM格带,和之前虛擬DOM做比對撤缴,雖然虛擬DOM的比對比真實DOM比對性能要快得多刹枉,但是能省略比對過程當然
可以解決更多的性能,shouldComponentUpdate就是做這件事用的
在React中想發(fā)送一個ajax請求:
不能再render函數(shù)中發(fā)送ajax請求屈呕,會造成死循環(huán)微宝,render函數(shù)會被反復執(zhí)行,只要在input框內(nèi)輸入內(nèi)容虎眨,render函數(shù)就會重新執(zhí)行蟋软,就重新發(fā)送一次ajax請求,這樣不合理嗽桩。ajax獲取一次數(shù)據(jù)就行了岳守,render函數(shù)會獲取很多次ajax
在React中,哪一個生命周期函數(shù)只會被執(zhí)行一次涤躲?
componentDidMount(){} 在組建被掛在到頁面上的時候态鳖,會被執(zhí)行一次榕吼,之后就不會再被重新執(zhí)行了,把ajax請求放到這里比較合適
思考:componentWillMount(){}也只執(zhí)行一次,把ajax請求放到這里可不可以柳刮?
也是沒有任何問題的伸但,但是當我們?nèi)憆eact native夸政,或者用React做服務(wù)器端同構(gòu)也就是更深一點的技術(shù)绘沉,可能會和以后一些更高端技術(shù)產(chǎn)生沖突。為了避免沖突岂昭,ajax就放到componentDidMount中以现,永遠都不會有任何問題。
思考:把ajax請求放到constructor中可不可以约啊?
也可以邑遏,因為constructor也是只執(zhí)行一次的函數(shù),推薦把ajax寫在componentWillMount中
在React項目中如何發(fā)送一個ajax請求恰矩?
React沒有內(nèi)置ajax记盒,需要安裝一些第三方模塊來幫助發(fā)送ajax請求。
npm install -g yarn
進入目錄
yarn add axios
src/TodoList.js
import React, { Component,Fragment } from 'react';
import TodoItem from './TodoItem';
import axios from 'axios'; //引入axios
import './style.css';
class Todolist extends Component {
constructor(props){
super(props)
this.state = {
inputValue:'',
list:[]
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">輸入內(nèi)容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
//使用axios
componentDidMount(){
axios.get('/api/todolist')
.then(()=>{alert('succ')})
.catch(()=>{alert('error')})
}
//構(gòu)建一個ref引用外傅,這個引用叫this.input指向?qū)?yīng)的input dom節(jié)點
getTodoItem(){
return this.state.list.map((item,index)=>{
return (
<TodoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
handleInputChange(e){
// console.log(e.target) //其實就是dom節(jié)點 <input id="insertArea" class="input" value="">
// const value = e.target.value //在外層保存一下
const value = e.target.value //e.target使用this.input來替換
this.setState(()=>({
inputValue: value
}));
}
handleBtnClick(){
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}));
}
handleItemDelete(index){
this.setState((prevState)=>{
const list = [...prevState.list];
list.splice(index,1)
return {list}
});
}
}
export default Todolist;
使用Charles實現(xiàn)本地數(shù)據(jù)mock
前后端分離纪吮,需要前端本地進行接口數(shù)據(jù)的模擬
下載安裝charles工具
桌面創(chuàng)建todolist.json,希望發(fā)送todolist接口的時候,能夠把桌面上的todolist.json返回回來
todolist.json
["學習React","學習英語","學習Vue"]
需要借助charles工具來解決這個問題
charles工具=>Tools=>Map Local
add 增加一條配置
charles可以抓到瀏覽器向外發(fā)送的一些請求萎胰,然后可以對一些請求做一些處理碾盟,比如看到請求地址
http://localhost:3000/api/todolist
,只要請求這個地址技竟,就把桌面文件返回,charles就是一個中間的代理服務(wù)器
componentDidMount(){
axios.get('/api/todolist')
.then((res)=>{
console.log(res.data)
// this.setState(()=>{
// return {
// list: res.data
// }
// })
this.setState(()=>({
list:[...res.data]
/構(gòu)建一個新的數(shù)組傳遞冰肴,避免不小心把res.data做了修改/
}))
})
.catch(()=>{alert('error')})
}
十:React中實現(xiàn)CSS過渡動畫
十一:Redux入門
想做大型應(yīng)用,需要在React基礎(chǔ)上配套一個數(shù)據(jù)層框架結(jié)合使用,全球范圍內(nèi)比較好的搭配數(shù)據(jù)層框架——Redux嚼沿。
Redux基礎(chǔ)設(shè)計理念:把組件之中的數(shù)據(jù)放到公用存儲區(qū)域存儲估盘,組件改變數(shù)據(jù)不需要傳遞瓷患,改變Store里的數(shù)據(jù)之后骡尽,其他組件會感知到Store數(shù)據(jù)發(fā)生改變,再去獲取數(shù)據(jù)擅编,這樣不管組件層次有多深攀细,走的流程相同。
Redux = Reducer + Flux
Redux的起源:React在2013年開源時爱态,F(xiàn)acebook團隊除了放出React框架谭贪,還放出了一個框架Flux,F(xiàn)lux框架是官方推出的最原始輔助React使用的數(shù)據(jù)層框架锦担。業(yè)界使用Flux發(fā)現(xiàn)一些缺點俭识,比如公共存儲區(qū)域Store可以有很多store組成,數(shù)據(jù)存儲的時候可能存在數(shù)據(jù)依賴問題洞渔,總之不是特別好用套媚,有人把Flux做了一個升級,升級成了目前使用的Redux磁椒。
在Redux里面除了借鑒Flux以前很多設(shè)計理念之外堤瘤,又引入了Reducer概念。
Redux的工作流程:
- Store存放了所有數(shù)據(jù)(存儲數(shù)據(jù)的公共區(qū)域)
- React Components(組件)從Store里拿數(shù)據(jù)浆熔,每個組件也要去改Store里數(shù)據(jù)
流程簡述一:
流程簡述二:
首先由一個組件本辐,組件要去獲取Store里一些數(shù)據(jù),和Store說"我要獲取數(shù)據(jù)"這句話就是Action Creators医增,Action Creators創(chuàng)建了一句話之后告訴Store慎皱,Store接收到"我要獲取數(shù)據(jù)",Store并不知道需要什么樣的數(shù)據(jù)叶骨,他去查一下茫多,Reducers知道應(yīng)該給你什么樣的數(shù)據(jù),Reducers告訴Store應(yīng)該給組件什么樣的數(shù)據(jù)邓萨,Store知道了之后把數(shù)據(jù)給到組件地梨。
使用Antd美化TodoList頁面:
Antd是React的UI框架。
yarn add antd
引入樣式:
import 'antd/dist/antd.css';
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
const data = [
'Racing car sprays burning fuel into crowd.',
'Japanese princess to wed commoner.',
'Australian walks 100km after outback crash.',
'Man charged over missing wedding girl.',
'Los Angeles battles huge wildfires.',
];
class TodoList extends Component {
render() {
return (
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<div>
<Input placeholder="todo info" style={{width: '300px',marginRight:'10px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px',width:'300px'}}
bordered
dataSource={data}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoList;
Antd工具在開發(fā)一些后臺管理系統(tǒng)的時候用的非常多缔恳。
創(chuàng)建Redux中的Store:
安裝Redux
yarn add redux
新建文件 src/store/index.js /倉庫(圖書管理員)
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer); // 把筆記本傳遞給store
export default store;
--------------------------------------------------------
新建文件 src/store/reducer.js /記錄本
const defaultState = {
inputValue:'111',
list:[1,2]
}
export default (state=defaultState,action)=>{ //函數(shù)接受兩個參數(shù)
return state;
}
/ reducer 是筆記本宝剖,筆記本存放很多關(guān)于圖書館數(shù)據(jù)操作,數(shù)據(jù)情況歉甚。
/ state 存放整個圖書館里所有書籍信息
/ state=defaultState 默認什么信息都不存儲
-----------------------------------------------------------------
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
import store from './store'; /簡化寫法 import store from './store/index.js';
class TodoList extends Component {
constructor(props){
super(props);
this.state = store.getState();
console.log(this.state) /通過store.getState() 獲取store中的數(shù)據(jù)
}
render() {
return (
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<div>
<Input value={this.state.inputValue} placeholder="todo info" style={{width: '300px',marginRight:'10px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px',width:'300px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoList;
Action和Reducer的編寫:
安裝工具 拓展程序 redux devtools
安裝完成之后万细,控制臺會多出redux選項
如何使用 redux devtools
src/store/index.js文件
// 在代碼中添加 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); // 把筆記本傳遞給store
/ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
/ 當做第二個參數(shù)傳遞進去,意思是如果有這個變量就執(zhí)行這個變量對應(yīng)的方法
/ __REDUX_DEVTOOLS_EXTENSION__這個變量是Redux DevTools的瀏覽器拓展
/ 意思是如果下面安裝了Redux DevTools,那么就在頁面上使用這個工具
export default store;
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
import store from './store';
class TodoList extends Component {
constructor(props){
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
store.subscribe(this.handleStoreChange)
//這個組件去訂閱Store,只要Store數(shù)據(jù)發(fā)生改變
//subscribe()里面寫一個函數(shù)赖钞,這個函數(shù)就被自動執(zhí)行
console.log(this.state) //通過store.getState() 獲取store中的數(shù)據(jù)
}
render() {
return (
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style={{width: '300px',marginRight:'10px'}}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px',width:'300px'}}
bordered
dataSource={this.state.list}
renderItem={(item,index) => (<List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>)}
/>
</div>
)
}
handleInputChange(e){
const action = {
type:'change_input_value',
value: e.target.value
}
store.dispatch(action)
// 把action 傳遞給Store
// Store需要去查小手冊(Reducers),把當前數(shù)據(jù)和action一起傳遞給小手冊(Reducers)
// Store會把當前Store存的數(shù)據(jù),和接收到的action一起轉(zhuǎn)發(fā)給reducers
// reducers來告訴Store來做什么
}
handleStoreChange(){
this.setState(store.getState())
//當感知到Store數(shù)據(jù)發(fā)生改變弓千,就調(diào)用store.getState()岂嗓,從store里從新取一次數(shù)據(jù)
//再調(diào)用setState 替換當前組件數(shù)據(jù)
}
handleBtnClick(){
const action = {
type:'add_todo_item'
}
store.dispatch(action)
}
handleItemDelete(index){
const action = {
type:'delete_todo_item',
index
}
store.dispatch(action)
}
}
export default TodoList;
src/store/reducer.js
const defaultState = {
inputValue:'',
list:[1,2]
}
//為什么需要深拷貝?
//Redux的限制,reducer可以接受state,但絕不能修改state
export default (state=defaultState,action)=>{ //函數(shù)接受兩個參數(shù)
console.log(state,action)
if(action.type === "change_input_value"){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.inputValue = action.value;
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
if(action.type === "add_todo_item"){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.list.push(newState.inputValue);
newState.inputValue="";
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
if(action.type === "delete_todo_item"){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.list.splice(action.index,1);
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
return state;
}
// reducer 是筆記本,筆記本存放很多關(guān)于圖書館數(shù)據(jù)操作,數(shù)據(jù)情況。
// state 存放整個圖書館里所有書籍信息
// state=defaultState 默認什么信息都不存儲
// state指的是上一次存儲數(shù)據(jù)
// action指的是用戶傳過來的那句話
ActionTypes的拆分:
新建src/store/actionTypes.js 文件存放 action Type
export const CHANGE_INPUT_VALUE = 'change_input_value'; //export 導出
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
使用:
src/TodoList.js
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './store/actionTypes';
CHANGE_INPUT_VALUE 替換 'change_input_value'
ADD_TODO_ITEM 替換 'add_todo_item'
DELETE_TODO_ITEM 替換 'delete_todo_item'
src/store/reducer.js
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './actionTypes';
CHANGE_INPUT_VALUE 替換 'change_input_value'
ADD_TODO_ITEM 替換 'add_todo_item'
DELETE_TODO_ITEM 替換 'delete_todo_item'
使用actionCreator 統(tǒng)一創(chuàng)建 action
新建 src/store/actionCreators.js
import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes';
export const getInputChangeAction = (value)=> ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = ()=> ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index)=> ({
type: DELETE_TODO_ITEM,
index
})
使用:
src/TodoList.js
import { getInputChangeAction,getAddItemAction,getDeleteItemAction } from './store/actionCreators'
handleInputChange(e){
// const action = {
// type: CHANGE_INPUT_VALUE,
// value: e.target.value
// }
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
}
handleBtnClick(){
// const action = {
// type: ADD_TODO_ITEM
// }
const action = getAddItemAction()
store.dispatch(action)
}
handleItemDelete(index){
// const action = {
// type: DELETE_TODO_ITEM,
// index
// }
const action = getDeleteItemAction(index)
store.dispatch(action)
}
好處:
- 提高代碼可維護性
- 方便前端自動測試化工具
Redux 知識點復習補充
Redux 設(shè)計和使用的三項原則:
- Store是唯一的
整個項目只有一個store 在src/store/index.js
(整個應(yīng)用中只有一個store公共存儲空間)
- 只有store能夠改變自己的內(nèi)容(在reducer中改變了store的數(shù)據(jù)是不被允許的,在reducer中是復制一個新的對象進行操作)
- Reducer必須是純函數(shù)
純函數(shù)指的是,給定固定的輸入,就一定會有固定的輸出,而且不會有任何副作用
state和action都確定的時候舌菜,那么return出來的結(jié)果永遠都是固定的,不純的函數(shù),比如newState.inputValue = new Date(),因為return newState的值不是固定的
副作用指 state.inputValue = action.value爹脾,這段代碼就是有副作用的代碼帖旨,對接受的參數(shù)做了修改
Redux 核心API
- createStore 創(chuàng)建store
- store.dispatch 派發(fā)action(action傳遞給Store)
- store.getState 獲取store數(shù)據(jù)內(nèi)容
- store.subscribe 訂閱store的改變
十二:Redux進階
UI組件和容器組件
UI組件(傻瓜組件)- 只負責頁面的一些顯示
容器組件(聰明組件)- 負責頁面邏輯處理
劃分組件的原因:把組件的邏輯和渲染放到一個組件中管理維護起來比較困難×榉粒或內(nèi)容比較多解阅。需要對組件進行一個拆分。
- UI組件負責頁面渲染
- 容器組件負責頁面邏輯
創(chuàng)建UI組件
src/TodoListUI.js
import React, { Component } from 'React';
import { Input,Button,List } from 'antd';
class TodoListUI extends Component {
render() {
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={this.props.inputValue}
placeholder="todo info"
style={{ width: '300px', marginRight: '10px' }}
onChange={this.props.handleInputChange}
/>
<Button
type="primary"
onClick={this.props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{ marginTop: '10px', width: '300px' }}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (<List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoListUI;
創(chuàng)建一個容器組件
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
import TodoListUI from './TodoListUI';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
store.subscribe(this.handleStoreChange)
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
/>
)
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState())
}
handleBtnClick() {
const action = getAddItemAction()
store.dispatch(action)
}
handleItemDelete(index) {
const action = getDeleteItemAction(index)
store.dispatch(action)
}
}
export default TodoList;
無狀態(tài)組件
上面的UI組件(傻瓜組件)只有一個render函數(shù)闷串,就可以用無狀態(tài)組件來定義這個組件瓮钥。
無狀態(tài)組件如何定義?
無狀態(tài)組件其實就是一個函數(shù)。
src/TodoListUI.js 改寫 UI組件改成無狀態(tài)組件
import React, { Component } from 'react';
import { Input,Button,List } from 'antd';
const TodoListUI = (props)=>{
// {/* this.props.inputValue 改成props.inputValue */}
// {/*不需要this 從傳參獲取*/}
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={props.inputValue}
placeholder="todo info"
style={{ width: '300px', marginRight: '10px' }}
onChange={props.handleInputChange}
/>
<Button
type="primary"
onClick={props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{ marginTop: '10px', width: '300px' }}
bordered
dataSource={props.list}
renderItem={(item, index) => (<List.Item onClick={()=>{props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
//接收一個props 同時要求返回一個JSX
// class TodoListUI extends Component {
// render() {
// return (
// <div style={{ marginTop: '10px', marginLeft: '10px' }}>
// <div>
// <Input
// value={this.props.inputValue}
// placeholder="todo info"
// style={{ width: '300px', marginRight: '10px' }}
// onChange={this.props.handleInputChange}
// />
// <Button
// type="primary"
// onClick={this.props.handleBtnClick}
// >
// 提交
// </Button>
// </div>
// <List
// style={{ marginTop: '10px', width: '300px' }}
// bordered
// dataSource={this.props.list}
// renderItem={(item, index) => (<List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>)}
// />
// </div>
// )
// }
// }
export default TodoListUI;
當一個普通組件只有render函數(shù)碉熄,可以使用無狀態(tài)組件替換掉普通組件
優(yōu)點:無狀態(tài)組件性能比較高桨武。
無狀態(tài)組件就是一個函數(shù),而class類生成對象還會有一些生命周期函數(shù)锈津,執(zhí)行起來需要執(zhí)行render和生命周期函數(shù)呀酸,執(zhí)行的東西遠比函數(shù)執(zhí)行的多。
注:雖然理論上UI組件只做頁面渲染琼梆,有時候簡單做一些邏輯也是可以的性誉。
Redux中發(fā)送異步請求獲取數(shù)據(jù)
componentDidMount 中獲取數(shù)據(jù)
todolist.json
["學習React","學習英語","學習Vue"]
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
ReactDOM.render(<TodoList />, document.getElementById('root'));
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction,initListAction } from './store/actionCreators'
import TodoListUI from './TodoListUI';
import axios from 'axios'; //引入第三方請求組件
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
store.subscribe(this.handleStoreChange)
//這個組件去訂閱Store,只要Store數(shù)據(jù)發(fā)生改變
//subscribe()里面寫一個函數(shù),這個函數(shù)就被自動執(zhí)行
console.log(this.state) //通過store.getState() 獲取store中的數(shù)據(jù)
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
/>
)
}
componentDidMount(){
axios.get('/todolist.json').then((res)=>{ //成功函數(shù)
const data = res.data; //接口獲取到數(shù)據(jù)茎杂,改變store中的數(shù)據(jù)
//store有個方法 dispatch 先要創(chuàng)建一個action
const action = initListAction(data)
store.dispatch(action)
//把action傳遞給store 错览,store拿到之前的數(shù)據(jù)連同action一同傳遞給reducer
console.log(data)
}).catch((fail)=>{ //失敗函數(shù)
})
}
handleInputChange(e) {
// const action = {
// type: CHANGE_INPUT_VALUE,
// value: e.target.value
// }
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
// 把action 傳遞給Store
// Store需要去查小手冊(Reducers),把當前數(shù)據(jù)和action一起傳遞給小手冊(Reducers)
// Store會把當前Store存的數(shù)據(jù),和接收到的action一起轉(zhuǎn)發(fā)給reducers
// reducers來告訴Store來做什么
}
handleStoreChange() {
this.setState(store.getState())
//當感知到Store數(shù)據(jù)發(fā)生改變煌往,就調(diào)用store.getState()倾哺,從store里從新取一次數(shù)據(jù)
//再調(diào)用setState 替換當前組件數(shù)據(jù)
}
handleBtnClick() {
// const action = {
// type: ADD_TODO_ITEM
// }
const action = getAddItemAction()
store.dispatch(action)
}
handleItemDelete(index) {
// const action = {
// type: DELETE_TODO_ITEM,
// index
// }
const action = getDeleteItemAction(index)
store.dispatch(action)
}
}
export default TodoList;
src/TodoListUI.js
import React, { Component } from 'react';
import { Input,Button,List } from 'antd';
const TodoListUI = (props)=>{
// {/* this.props.inputValue 改成props.inputValue */}
// {/*不需要this 從傳參獲取*/}
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={props.inputValue}
placeholder="todo info"
style={{ width: '300px', marginRight: '10px' }}
onChange={props.handleInputChange}
/>
<Button
type="primary"
onClick={props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{ marginTop: '10px', width: '300px' }}
bordered
dataSource={props.list}
renderItem={(item, index) => (<List.Item onClick={()=>{props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
//接收一個props 同時要求返回一個JSX
// class TodoListUI extends Component {
// render() {
// return (
// <div style={{ marginTop: '10px', marginLeft: '10px' }}>
// <div>
// <Input
// value={this.props.inputValue}
// placeholder="todo info"
// style={{ width: '300px', marginRight: '10px' }}
// onChange={this.props.handleInputChange}
// />
// <Button
// type="primary"
// onClick={this.props.handleBtnClick}
// >
// 提交
// </Button>
// </div>
// <List
// style={{ marginTop: '10px', width: '300px' }}
// bordered
// dataSource={this.props.list}
// renderItem={(item, index) => (<List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>)}
// />
// </div>
// )
// }
// }
export default TodoListUI;
src/store/actionCreators.js
import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes';
export const getInputChangeAction = (value)=> ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = ()=> ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index)=> ({
type: DELETE_TODO_ITEM,
index
})
export const initListAction = (data) => ({ //接受list.json接口數(shù)據(jù)
type: INIT_LIST_ACTION,
data
})
src/store/actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'; //export 導出
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
export const INIT_LIST_ACTION = 'init_list_action'; //接受list.json 定義常量
src/store/index.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); // 把筆記本傳遞給store
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
// 當做第二個參數(shù)傳遞進去,意思是如果有這個變量就執(zhí)行這個變量對應(yīng)的方法
// __REDUX_DEVTOOLS_EXTENSION__這個變量是Redux DevTools的瀏覽器拓展
// 意思是如果下面安裝了Redux DevTools刽脖,那么就在頁面上使用這個工具
export default store;
src/store/reducer.js
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION} from './actionTypes';
const defaultState = {
inputValue:'',
list:[]
}
//為什么需要深拷貝羞海?
//Redux的限制,reducer可以接受state,但絕不能修改state
export default (state=defaultState,action)=>{ //函數(shù)接受兩個參數(shù)
console.log(state,action)
if(action.type === CHANGE_INPUT_VALUE){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.inputValue = action.value;
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
if(action.type === ADD_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.list.push(newState.inputValue);
newState.inputValue="";
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
if(action.type === DELETE_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.list.splice(action.index,1);
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
if(action.type === INIT_LIST_ACTION){
const newState = JSON.parse(JSON.stringify(state)) //深拷貝
newState.list = action.data
return newState //返回給Store,替換Store的老數(shù)據(jù)
}
return state;
}
// reducer 是筆記本,筆記本存放很多關(guān)于圖書館數(shù)據(jù)操作曲管,數(shù)據(jù)情況却邓。
// state 存放整個圖書館里所有書籍信息
// state=defaultState 默認什么信息都不存儲
// state指的是上一次存儲數(shù)據(jù)
// action指的是用戶傳過來的那句話
使用Redux-thunk中間件進行ajax請求發(fā)送
ajax異步或者復雜邏輯放在組件里實現(xiàn)時,組件會顯得過于臃腫院水,希望移除到其他地方統(tǒng)一管理腊徙。
Redux-thunk中間件可以把異步請求或復雜邏輯放到action中處理
Redux-thunk是Redux的一個中間件。
在React中Redux-thunk這個中間件用的非常多衙耕。
Redux-thunk:github地址
yarn add redux-thunk //安裝
使用 在store中
import { createStore, applyMiddleware } from 'redux'; 要引入 applyMiddleware
import thunk from 'redux-thunk'; //引入redux-thunk模塊
import rootReducer from './reducers/index';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk) //第二個參數(shù)引入
);
意思是 創(chuàng)建Store時昧穿,使用reducer構(gòu)建初始數(shù)據(jù),然后創(chuàng)建store時橙喘,store會使用一個中間件thunk
我們說的中間件时鸵,是redux的中間件,不是react的
引入多個中間件
引入thunk 以及 redux-devtools
githup 搜索 redux-devtools 查到文檔
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
);
使用
通過這種編碼厅瞎,既支持window下的devtools,同時也引入了Redux-thunk
src/store/index.js
import { createStore,applyMiddleware ,compose } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// 如果window的__REDUX_DEVTOOLS_EXTENSION_COMPOSE__存在饰潜,就去調(diào)用一下這個方法
// 否則就等于一個compose函數(shù),compose函數(shù)需要從redux中引入進來
const enhancer = composeEnhancers( //讓上面存儲的composeEnhancers和簸,執(zhí)行一下
// 順便把thunk通過applyMiddleware執(zhí)行一下彭雾,傳遞進去
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
// 實際上 __REDUX_DEVTOOLS_EXTENSION__ 也是redux的中間件
// 我們說的中間件,是redux的中間件
export default store;
上面代碼只做了一件事锁保,安裝了thunk,然后在store創(chuàng)建的時候薯酝,使用了thunk半沽,代碼從GitHub對應(yīng)的指南里面拷貝過來的
配置好了之后,圍繞redux-thunk來編寫代碼
src/TodoList.js
把異步操作的代碼從組件中移除吴菠,移除到action里面
import { getInputChangeAction, getAddItemAction, getDeleteItemAction,getTodoList } from './store/actionCreators'
// 引入getTodoList
componentDidMount(){
const action = getTodoList();
store.dispatch(action)
/dispatch了一個函數(shù)者填,實際上store只能接受一個對象
/store發(fā)現(xiàn)是一個函數(shù)就會干一件事,幫助自動執(zhí)行一下對應(yīng)的函數(shù)
/action對應(yīng)的函數(shù)是actionCreators.js 中g(shù)etTodoList 的 return 函數(shù)
// axios.get('/todolist.json').then((res)=>{
// const data = res.data;
// const action = initListAction(data)
// store.dispatch(action)
// }).catch((fail)=>{})
// 把異步操作的代碼從組件中移除做葵,移除到action里面
}
當使用了Readux-thunk之后 action可以是一個函數(shù)了(只有用了thunk之后占哟,才能是函數(shù))
getTodoList 的 rerun 函數(shù)再去取json的數(shù)據(jù),獲取數(shù)據(jù)酿矢,去改變store中的數(shù)據(jù)
只要改變store中的數(shù)據(jù)榨乎,又要走redux的流程
去調(diào)用之前寫的initListAction,去創(chuàng)建action
想去調(diào)用store.dispatch方法
store.dispatch方法如何獲取到瘫筐?
返回的函數(shù)自動就會接收dispatch方法
只要調(diào)用dispatch 把 action派發(fā)出去就行
這個action 實際是一個對象
store會判斷action是一個對象蜜暑,直接就接收這個對象,改變原始狀態(tài)
src/store/actionCreators.js
import axios from 'axios'; //引入第三方請求組件
/* ------------未變化------------- */
import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes';
export const getInputChangeAction = (value)=> ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = ()=> ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index)=> ({
type: DELETE_TODO_ITEM,
index
})
export const initListAction = (data) => ({ //接受list.json接口數(shù)據(jù)
type: INIT_LIST_ACTION,
data
})
/* ------------未變化------------- */
// 當使用了Readux-thunk之后 action可以是一個函數(shù)了
export const getTodoList = () => { //調(diào)用getTodoList生成內(nèi)容是函數(shù)的action時
return (dispatch) => { // 這個函數(shù)能夠接收到dispatch方法
axios.get('/todolist.json').then((res) => {
const data = res.data;
const action = initListAction(data)
dispatch(action) //直接調(diào)用dispatch方法即可
}).catch((fail)=>{})
}
// 正常來說严肪,return應(yīng)該是一個對象 但使用了Readux-thunk之后史煎,return結(jié)果可以是一個函數(shù)
// 在這個函數(shù)里面 可以做異步操作
}
疑問:把ajax放到componentDidMount中不是挺好的,為什么要這么麻煩
如果把異步函數(shù)放到組件的生命周期函數(shù)中來做驳糯,這個生命周期函數(shù)有可能變得越來越復雜,越來越多氢橙,這個組件會變得越來越大酝枢,所以建議把復雜的業(yè)務(wù)邏輯或異步函數(shù)拆分到一個地方去管理,現(xiàn)在借助Redux-thunk就可以放到actionCreators中去管理了
放到這里管理又帶來一個額外好處悍手,做自動化測試的時候帘睦,去測試getTodoList方法會非常的簡單,比測試組件的生命周期函數(shù)要簡單的多坦康。
反復寫5遍中間件的使用流程竣付,捋清流程