react入門學習實現(xiàn)一個TodoList

一 : 腳手架

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 value={this.state.inputValue} /> 時,雖然頁面可以正常顯示纺涤,但是控制臺會出現(xiàn)如上報錯译暂。

  • 如果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}}  

dangerouslySetInnerHTML實現(xiàn)的功能

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開發(fā)的網(wǎng)站

React開發(fā)環(huán)境

線上React開發(fā)的網(wǎng)站

安裝完成之后,控制臺會增加一個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插件使用:

打開F12,勾選中Highlight Updates,每次組件被更新蛙吏,會有閃框提示

每次輸入內(nèi)容源哩,子組件都會更新

對于一個組件,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 我的子組件被渲染一次之后坷檩,如果子組件需要被更新却音,那么強制要求不更新*/}
子組件只會被render一次,之后不會被重新渲染

上面寫法不是最優(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 增加一條配置


請求成功

查看數(shù)據(jù)

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的工作流程:

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ù)


實現(xiàn)從/todolist.json借口獲取數(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遍中間件的使用流程竣付,捋清流程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滞欠,隨后出現(xiàn)的幾起案子古胆,更是在濱河造成了極大的恐慌,老刑警劉巖筛璧,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逸绎,死亡現(xiàn)場離奇詭異,居然都是意外死亡夭谤,警方通過查閱死者的電腦和手機棺牧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朗儒,“玉大人颊乘,你說我怎么就攤上這事参淹。” “怎么了乏悄?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵浙值,是天一觀的道長。 經(jīng)常有香客問我纲爸,道長亥鸠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任识啦,我火速辦了婚禮负蚊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颓哮。我一直安慰自己家妆,他們只是感情好,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布冕茅。 她就那樣靜靜地躺著伤极,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姨伤。 梳的紋絲不亂的頭發(fā)上哨坪,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音乍楚,去河邊找鬼当编。 笑死,一個胖子當著我的面吹牛徒溪,可吹牛的內(nèi)容都是我干的忿偷。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼臊泌,長吁一口氣:“原來是場噩夢啊……” “哼鲤桥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起渠概,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤茶凳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后高氮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慧妄,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年剪芍,在試婚紗的時候發(fā)現(xiàn)自己被綠了塞淹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡罪裹,死狀恐怖饱普,靈堂內(nèi)的尸體忽然破棺而出运挫,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站笋额,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匈挖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一康愤、第九天 我趴在偏房一處隱蔽的房頂上張望儡循。 院中可真熱鬧,春花似錦征冷、人聲如沸择膝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肴捉。三九已至,卻和暖如春叔收,著一層夾襖步出監(jiān)牢的瞬間齿穗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工饺律, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缤灵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓蓝晒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帖鸦。 傳聞我的和親對象是個殘疾皇子芝薇,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容