Deng
Deng
React开发简书项目 | odjBlog
    欢迎来到odjBlog的博客!

React开发简书项目

web前端学习 odjbin 4年前 (2022-05-12) 81次浏览 0个评论

2.React 初深

React 开发环境准备

工程目录文件简介

  • App.js
import React,{Component} from "react";
// import {Component} from 'react'
// // 等价于
// import React from "react";
// const Component=React.Component

class App extends Component{
  render(){
    return(
      <div>
        hell world
      </div>
    )
  }
}

export default App;
  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />,document.getElementById('root'));

React 中最基础的 JSX 语法

  • JSX 语法中,如果我们要使用自己创建的组件,组件开头必须是大写开头
class App extends Component{
 render(){
    //JSX 语法:不需要加'<div>xx</div>' 引号
    return <div>hello world</div>;   
  }
 }

3. React 基础精讲

使用 React 编写 TodoList 功能

  • src-TodoList.js
import React,{Component,Fragment} from "react";

class TodoList extends Component{
  render(){
    return(
      //在 JSX 语法中要求一个组件 render 函数返回的内容,外层必须有个包裹元素
      //最外层必须整体包含在一个大的元素中 Fragment:占位符
      <Fragment>
      <div><input/><button>提交</button>  </div>
      <ul>
        <li>学英语</li>
        <li>Learn English</li>
      </ul>
      </Fragment>
    )
  }
}
export default TodoList;

React 中的响应式设计思想和事件绑定

  • src-TodoList.js
import React,{Component,Fragment} from "react";

class TodoList extends Component{

  constructor(props){
    super(props);
    this.state={
      inputValue:'',
      list:[]
    }
  }

  render(){
    return(
      <Fragment>
      <div>
        <input 
          value={this.state.inputValue}
          onChange={this.handleInputChange.bind(this)}
        />
        <button>提交</button>  
      </div>
      <ul>
        <li>学英语</li>
        <li>Learn English</li>
      </ul>
      </Fragment>
    )
  }
  handleInputChange(e){
    //this 指向有问题 undefined ↑↑↑ .bind(this)
    this.setState({
      inputValue:e.target.value
    })
  }
}
export default TodoList;

实现 TodoList 新增删除功能

 <ul>
        {
          this.state.list.map((item,index)=>{
            return <li 
              key={index} 
              onClick={this.handleItemDelete.bind(this,index)}
              >{item}
            </li>
          })
        }
      </ul>
=============================
  //新增
  handleBthClick(){
    this.setState({
      list:[...this.state.list,this.state.inputValue],
      inputValue:''
    })
  }
  //删除
  handleItemDelete(index){
    //immutable: state 不允许我们做任何的改变××[this.state.list.splice(index,1)]××
    const list=[...this.state.list];
    list.splice(index,1);

    this.setState({
      list:list
    })
  }

JSX 语法细节补充

  • 1.注释写法:
    • {/ 注释 /}
    • 单行注释
      {
      //注释
      }
  • 2.编译 html 标签
             <li 
              key={index} 
              onClick={this.handleItemDelete.bind(this,index)}
              dangerouslySetInnerHTML={{__html:item}}
              ></li>

  • 点输入内容,,光标会聚焦
 <label htmlFor="insertArea">输入内容</label>
        <input 
          id="insertArea"
          />

拆分组件与组件之间的传值

  • 父组件向子组件传递内容,通过属性来传递
TodoList.js
<ul>
        {
          this.state.list.map((item,index)=>{
            return (
            <div>
              <TodoItem content={item}/>
            </div>
            )
          })
        }
      </ul>
TodoItem.js
import React,{Component} from "react";

class TodoItem extends Component{
  render(){
    return <div>{this.props.content}</div>
  }
}
export default TodoItem;
  • 子组件如何向父组件传递数据 [删除]
TodoList.js
 <TodoItem 
                content={item} 
                index={index}
                deleteItem={this.handleItemDelete.bind(this)}
              />
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;

TodoList 代码优化

TodoItem.js
  handleClick(){
    const {deleteItem,index} =this.props;
    deleteItem(index)
    // this.props.deleteItem(this.props.index)
  }
  • 引入样式在组件后面
  • this.handleBthClick=this.handleBthClick.bind(this)

  • 异步
 handleInputChange(e){
    const value=e.target.value
    this.setState(()=>({
        inputValue:value
    }))
  }

  //新增
  handleBthClick(){
    this.setState((prevState)=>({
      list:[...prevState.list,prevState.inputValue],
      inputValue:''
    }))
  }

   //删除
  handleItemDelete(index){
    this.setState((prevState)=>{
      const list=[...prevState.list]
      list.splice(index,1);
      return {list}
    });
  }

围绕 React 衍生出的思考

  • 声名式开发
  • 可以与其他框架并存
  • 组件化开发
  • 单向数据流
  • 视图层框架

4.React 高级内容

Reactdevelopertools 安装及使用

  • git clone -b v3 https://github.com/facebook/react-devtools
  • npm install
  • npm run build:extension:chrome
  • 步骤:更多工作-> 扩展程序-> 开启开发者模式-> 点击加载已解压的扩展程序
  • 然后选择存放插件的文件:依次选择 react-devtools->shells->chrome->build->unpacked
  • 注:安装插件后刷新页面图标就会正常亮起

PropTypes 与 DefaultProps 的应用

import PropTypes from 'prop-types';
 render(){
    const {content,test} =this.props;
    return (
    <div onClick={this.handleClick}>
      {test}-{content}
    </div>
    )
  }

TodoItem.propTypes={
  test:PropTypes.string.isRequired,
  //arrayOf : 可以提供一个或者的语法
  content:PropTypes.arrayOf(PropTypes.number,PropTypes.string),
  deleteItem:PropTypes.func,
  //number->string : 限制要求父组件给我这个子组件传值的时候传值类型是什么类型的
  index:PropTypes.number
}
TodoItem.defaultProps={
  test:'hello world'
}

props,state 与 render 函数的关系

Test.js
import React,{Component} from "react";

class Test extends Component{
  //当父组件的 render 函数被运行时,它的子组件的 render 都将被重新运行一次
  render(){
    console.log('Test render');
    return <div>{this.props.content}</div>
  }
}
export default Test;

TodoList.js:
  constructor(props){
    super(props);
    //当组件的 state 或者 props 发生改变的时候,render 函数就会重新执行

React 中的虚拟 DOM

  • 什么是虚拟 DOM?

  • (1)
    1.state 数据
    2.JSX 模板
    3.数据+模板 结合,生成真实的 DOM,来显示
    4.state 发生改变
    5.数据+模板结合,生成真实的 DOM,替换原始的 DOM

    缺陷:
    第一次生成了一个完整的 DOM 片段
    第二次生成了一个完整的 DOM 片段
    第二次的 DOM 替换第一次的 DOM,非常耗性能

  • (2)
    1.state 数据
    2.JSX 模板
    3.数据+模板 结合,生成真实的 DOM,来显示
    4.state 发生改变
    5.数据+模板结合,生成真实的 DOM,并不直接替换原始的 DOM
    6.新的 DOM (DocumentFragment) 和原始的 DOM 做比对,找差异
    7.找出 input 框发生了变化
    8.只用新的 DOM 中的 input 元素,替换掉老的 DOM 中的 input 元素
    缺陷:
    性能的提示并不明显

  • (3)

1.state 数据
2.JSX 模板

3.数据+模板 生成虚拟 DOM (虚拟 DOM 就是一个 JS 对象,用它来描述真实 DOM) (损耗了性能)
    ['div' , {id: 'abc' }, ['span ' , { }, 'hello world']]

4. 用虚拟 DOM 的结构生成真实的 DOM,来显示
    <div id='abc'><span>hello world</span>/div>

5.state 发生变化

6.数据 + 模板 生成新的虚拟 DOM (极大的提升了性能)
    ['div' , {id: 'abc' }, ['span' , { }, 'bye bye']]

7. 比较原始虚拟 DOM 和新的虚拟 DOM 的区别,找到区别是 span 中内容 (极大的提升了性能)

8.直接操作 DOM, 改变 span 中的内容
  • 面试,虚拟 DOM 是什么
    本质上就是 JS 对象,之所以能提高性能本质上是业务什么呢? js 对象不怎么耗性能,比较真实的 DOM 会很耗性能

深入了解虚拟 DOM

优点:
1.性能提升了
2.它使得跨端应用得以实现 React Native

虚拟 DOM 中的 Diff 算法

  • setState 是异步的, 本质上是 提升 react 底层的性能
  • Diff 算法 :

  • 算法简单所带来的好处 : 比对速度非常快,可能会造成 dom 重新渲染的浪费,大大的减少两个虚拟 dom 之间比对的算法上的性能消耗,所以react采用了一个同层的虚拟 dom 的比对算法

React 中 ref 的使用

  • 尽量不要操作 DOM
 render(){
    return(
      <Fragment>
      <div>
        <label htmlFor="insertArea">输入内容</label>
        <input 
          id="insertArea"
          className="input"
          value={this.state.inputValue}
          onChange={this.handleInputChange}
          ref={(input)=>{this.input=input}}
        />
        <button onClick={this.handleBthClick}>提交</button>  
      </div>
      <ul ref={(ul)=>{this.ul=ul}}>
        {this.getTodoItem()}
      </ul>
      </Fragment>
    )
  }

   // 获取 inputvalue 值
  handleInputChange(){
    const value=this.input.value
    this.setState(()=>({
        inputValue:value
    }));
  }

  //新增
  handleBthClick(){
    //setState : 异步函数,不会立即被执行
    this.setState((prevState)=>({
      list:[...prevState.list,prevState.inputValue],
      inputValue:''
    }));
    //console.log 比 setState 底层运行之前先执行
    console.log(this.ul.querySelectorAll('div').length)
  }
  • 有时候如果 setState 是一个异步函数,就没办法正确输出数据变更后页面 DOM 的情况,,[就不太合理]
  • 一旦你希望 setState 执行成功后,页面更新之后去获取页面的 DOM,,正确的做法是 :
  • 当函数执行的时候,页面已经被完整的更新完了,,获取页面的 DOM,是没错的
  //新增
  handleBthClick(){
    //setState : 异步函数,不会立即被执行
    this.setState((prevState)=>({
      list:[...prevState.list,prevState.inputValue],
      inputValue:''
    }),()=>{
      console.log(this.ul.querySelectorAll('div').length)
    });
  }
  • ref : 帮助我们在 react 直接获取 DOM 元素的时候来使用的,,一般情况下,尽量不要用 ref, 有的时候在做机器复杂的业务,比如说一些动画的时候,不可避免还是要用到 ref 中的 DOM 标签,,用 ref 来获取

React 的生命周期函数

  • 生命周期函数指在某一个时刻组件会自动调用执行的函数
//在组件即将被挂载到页面的时候自动执行
  componentWillMount(){
    console.log('componentWillMount');
  }

  render(){}

 //组件被挂载到页面之后,自动被执行
  componentDidMount(){
    console.log('componentDidMount');
  }

//组件被更新之前,他会自动被执行
  shouldComponentUpdate(){
    console.log('shouldComponentUpdate');
    return true
  }

 //组件被更新之前,他会自动被执行,但是他在 shouldComponentUpdate 之后被执行
  //如果 shouldComponentUpdate 返回 true,它才执行;返回 false,这个函数就不会被执行了
  componentWillUpdate(){
    console.log('componentWillUpdate');
  }

  //组件更新完成之后,它会被执行
  componentDidUpdate(){
    console.log('componentDidUpdate');
  }
  • TodoItem.js
 // 一个组件要从父组件接受参数
  // 如果这个组件第一次存在于父组件中,不会执行
  // 如果这个组件之前已经存在于父组件中,才会执行
  componentWillReceiveProps(){
    console.log('child componentWillReceiveProps');
  }

  // 当这个组件即将被从页面中剔除的时候,会被执行
  componentWillUnmount(){
    console.log('child componentWillUnmount');
  }

React 生命周期函数的使用场景

  • 降低性能:
    • this.handleClick=this.handleClick.bind(this);
    • react 的底层 setState 是一个异步函数, 可以把多次数据改变,结合成一次来做,,降低虚拟 DOM 的比对频率
    • 可以避免无谓的组件的 render 函数的运行
TodoItem.js shouldComponentUpdate(nextProps,nextState){
    if(nextProps.content!==this.props.content){
      return true;
    }else{
      return false;
    }
  }
  • npm i axios
TodoList.js
import axios from "axios";
render(){}
   //只执行一次: 在组件被挂载到页面的时候,会被执行一次,之后就不会被重新重复执行了
  // 将 ajax 请求 就放到这里,永远不会有问题
 componentDidMount(){
    axios.get('/api/todolist')
    .then(()=>{
      alert('succ')
    }).catch(()=>{
      alert('error')
    })
  }

使用 Charles 进行接口数据模拟

TodoList.js
 componentDidMount(){
    axios.get('/api/todolist')
    .then((res)=>{
     this.setState(()=>({
       list:[...res.data]
     }))
    }).catch(()=>{
      alert('error')
    })
  }

React 的 CSS 过渡动画

App.js
import React, { Component, Fragment } from "react";
import './style.css';

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      show: true
    }
    this.handleToggole = this.handleToggole.bind(this)
  }

  render () {
    return (
      <Fragment>
        <div className={this.state.show ? 'show' : 'hide'}>hello</div>
        <button onClick={this.handleToggole}>toggole</button>
      </Fragment>
    )
  }

  handleToggole () {
    this.setState({
      show: this.state.show ? false : true
    })
  }
}
export default App;
style.css
 .show {
   opacity: 1;
   transition: all 1s ease-in;
 }

 .hide {
   opacity: 0;
   transition: all 1s ease-in;
 }

React 中使用 CSS 动画效果

 .hide {
  /* forwards : 动画结束之后,保存动画最后一帧的 CSS 样式 */
   animation: hide-item 2s ease-in forwards;
 }

 @keyframes hide-item {
   0% {
     opacity: 1;
     color: red;
   }

   50% {
     opacity: 0.5;
     color: green;
   }

   100% {
     opacity: 0;
     color: blue;
   }
 }
  • 渐变动画

使用 react-transition-group 实现动画(1)

import React, { Component, Fragment } from "react";
import { CSSTransition } from 'react-transition-group';
import './style.css';

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      show: true
    }
    this.handleToggole = this.handleToggole.bind(this)
  }

  render () {
    return (
      <Fragment>
        <CSSTransition
          in={this.state.show}
          timeout={1000}
          classNames='fade'
          unmountOnExit
          onEntered={(el)=>{el.style.color='blue'}}
          appear={true}
        >
          <div>hello</div>
        </CSSTransition>
        <button onClick={this.handleToggole}>toggole</button>
      </Fragment>
    )
  }

  handleToggole () {
    this.setState({
      show: this.state.show ? false : true
    })
  }
}
export default App;
  • style.css
.fade-enter , .fade-appear{
  opacity: 0;
}

.fade-enter-active, .fade-appear-active {
  opacity: 1;
  transition: opacity 1s ease-in;
}

.fade-enter-done {
  opacity: 1;
}

.fade-exit {
  opacity: 1;
}

.fade-exit-active {
  opacity: 0;
  transition: opacity 1s ease-in;
}

.fade-exit-done {
  opacity: 0;
}

使用 react-transition-group 的使用(2)

  • 多个 dom 元素的动画
import React, { Component, Fragment } from "react";
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './style.css';

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      list: []
    }
    this.handleAddItem = this.handleAddItem.bind(this)
  }

  render () {
    return (
      <Fragment>
        <TransitionGroup>
          {

            this.state.list.map((item, index) => {
              return (
                <CSSTransition
                  timeout={1000}
                  classNames='fade'
                  unmountOnExit
                  onEntered={(el) => { el.style.color = 'blue' }}
                  appear={true}
                  key={index}
                >
                  <div>{item}</div>
                </CSSTransition>
              )
            })
          }
        </TransitionGroup>
        <button onClick={this.handleAddItem}>toggole</button>
      </Fragment>
    )
  }

  handleAddItem () {
    this.setState((prevState) => {
      return {
        list: [...prevState.list, 'item']
      }
    })
  }
}
export default App;

5.Redux 入门

Redux 概念简述

  • Redux = Reducer + Flux

Redux 的工作流程

使用 Antd 实现 TodoList 页面布局

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;

5-4 创建 redux 中的 store_x264

  • npm i redux

  • src -> store -> index.js

import { createStore } from "redux";
// 笔记本 传给 store 管理员
import reducer from "./reducer";

const store=createStore(reducer);
export default store;
  • src -> store -> reducer.js
const defaultState={
  inputValue:'',
  list:[]
}
// reducer 是笔记本
// state:存放的是整个图书馆所有书籍的信息
// eslint-disable-next-line import/no-anonymous-default-export
export default (state = defaultState,action)=>{
  return state;
}
  • TodoList.js

报错
Failed to parse source map: 'webpack://antd/./components/time-picker/style/i
import 'antd/dist/antd.css'; -> import 'antd/dist/antd.min.css';

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button,List } from 'antd';
// 组件的数据从 store 中取
import store from './store/index';

class TodoList extends Component {

  constructor(props){
    super(props);
    this.state=store.getState();
  }

  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;

5-5Action 和 Reducer 的编写

  • src -> store -> index.js
import { createStore } from "redux";
// 笔记本 传给 store 管理员
import reducer from "./reducer";
// 引入 redux-devtools 需要用到的依赖包
import { composeWithDevTools } from 'redux-devtools-extension'

const store=createStore(reducer,composeWithDevTools());

export default store;
  • src -> store -> reducer.js
const defaultState = {
    inputValue: '123',
    list: [1, 2]
}
// reducer 是笔记本
// state:存放的是整个图书馆所有书籍的信息
//reducer 可以接收 state,但是绝不能修改 state
// eslint-disable-next-line import/no-anonymous-default-export
export default (state = defaultState, action) => {
    //结合之前的数据和当前用户的操作告诉 store,现在新的数据应该变成什么样
    if (action.type === 'change_input_value') {
        const newState = JSON.parse(JSON.stringify(state));
        newState.inputValue = action.value;
        return newState;
    }
    if (action.type==='add_todo_item'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue)
        newState.inputValue=''
        return newState;
    }
    console.log(state, action);
    return state;
}
  • TodoList.js
import React, {Component} from 'react';
import 'antd/dist/antd.min.css';
import {Button, Input, List} from 'antd';
// 组件的数据从 store 中取
import store from './store/index.js';

class TodoList extends Component {

    constructor(props) {
        super(props);
        this.state = store.getState();
        this.handleInputChange = this.handleInputChange.bind(this)
        this.handleStoreChange = this.handleStoreChange.bind(this)
        this.handleBtnClick = this.handleBtnClick.bind(this)
        store.subscribe(this.handleStoreChange);
    }

    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 => <List.Item>{item}</List.Item>}
                />
            </div>
        )
    }

    handleInputChange(e) {
        const action = {
            type: 'change_input_value',
            value: e.target.value
        }
        store.dispatch(action)
    }

    handleStoreChange() {
        this.setState(store.getState());
    }

    handleBtnClick() {
        const aciton = {
            type: 'add_todo_item'
        };
     store.dispatch(aciton);
    }
}
export default TodoList;

5-6 使用 Redux 完成 TodoList 删除功能

  handleItemDelete(index){
        const action={
            type:'delete_todo_item',
            index
        }
        store.dispatch(action);
    }

5-7ActionTypes 的拆分

  • 防止一些拼写问题,,常量和变量系统会报错,,有利于找错
  • store -> actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_TODO_ITEM = 'add_todo_item'
export const DELETE_TODO_ITEM = 'delete_todo_item'

import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM } from './actionTypes'

5-8 使用 actionCreator 统一创建 action

  • 利于管理和测试,,前端有自动化可测试工具, ,利于代码的可维护性

  • actionCreator.js

import {ADD_TODO_ITEM, CHANGE_INPUT_VALUE, 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
})
  • Todolist.js
handleInputChange(e) {
        const action=getInputChangeAction(e.target.value)
        store.dispatch(action)
    }

5-9Redux 知识点复习补充

  • 利于管理和测试,,前端有自动化可测试工具, ,利于代码的可维护性

  • actionCreator.js

import {ADD_TODO_ITEM, CHANGE_INPUT_VALUE, 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
})
  • Todolist.js
handleInputChange(e) {
        const action=getInputChangeAction(e.target.value)
        store.dispatch(action)
    }

5-9Redux 知识点复习补充

  • store 是唯一的
  • 只有 store 能够改变自己的内容
  • Reducer 必须是纯函数

    • 纯函数 : 给定固定的输入,就一定会有固定的输出,而且不会有任何副作用
    • 注:
      ①一旦有 setTimeout,日期相关内容,ajax 请求存在的,不再是纯函数,不能有异步操作,不能有跟时间相关的操作


    ②这就是有副作用的代码,,对接收的参数做了修改,不是纯函数了

Redux 的核心 API

  • createStore : 帮助我们创建 store
  • store.dispatch : 帮助我们派发 action,这个 action 会传递给 store
  • store.getState : 帮助我们获取到 store 中所有的数据内容
  • store.subscribe : 帮助我们订阅 store 的改变,一旦改变,这接收的回调函数就会被执行

    6.Redux 进阶

    6-01UI 组件(傻瓜组件)和容器组件

  • TodoListUI.js 页面渲染
  • 容器组件 TodoList.js 业务逻辑代码,负责功能实现

    Redux 中发送异步请求获取数据

  • 在 charles->tools->Map Local...->Add->

export const initListAction=(data)=>({
    type:INIT_LIST_ACTION,
    data
})
  • actionTypes.js
export const INIT_LIST_ACTION = 'init_list_action'
  • Todolist.js
import axios from 'axios'
 componentDidMount() {
    axios.get('/list.json').then((res) => {
      const data=res.data;
      const action=initListAction(data);
      store.dispatch(action)
    })
  }

使用 Redux-thunk 中间件进行 ajax 请求发送

export const getTodolist = () => {
  return (dispatch) => {//return 一个函数,可以做异步操作
    axios.get('/list.json').then((res) => {
      const data = res.data;
      const action = initListAction(data);
      dispatch(action)
    })
  }
}
  • src/TodoList.js
//建议将异步函数和复杂逻辑拆分到,借助 redux-thunk
  componentDidMount() {
    const action=getTodolist()//函数
    //当你调用 store.dispatch,把 action 发给 store 的时候,action 就会被自动执行
    store.dispatch(action);
  }

什么是 Redux 的中间件

Redux-saga 中间件使用入门

如何使用 React-redux

7.项目实战: Header 组件开发

使用 styled-components 完成 Header 组件布局

  • background-size: contain 与 cover 的区别:
  • 在 no-repeat 情况下,如果容器宽高比与图片宽高比不同,
  • cover:图片宽高比不变、铺满整个容器的宽高,而图片多出的部分则会被截掉;
  • contain:图片自身的宽高比不变,缩放至图片自身能完全显示出来,所以容器会有留白区域;
  • PS:其实,从英文的意思来说:cover 意味着“遮罩、遮盖”---此处理解为“塞满”较恰当,contain 意为“包含”--也就是:我图片虽然缩放了,但是整个图是被“包含”在你里面的,你必须把我显示完整、不能裁剪我一丝一毫~
  • 在 repeat 情况下:cover:与上述相同;contain:容器内至少有一张完整的图,容器留白区域则平铺背景图,铺不下的再裁掉。

使用 React-Redux 进行应用数据的管理????????[数据绕]

ajax 获取推荐数据

  • store/actionCreators.js
import {fromJS} from "immutable";
import axios from "axios";
const changeList=(data)=>({
  type:constants.CHANGE_LIST,
  data:fromJS(data)
})

export const getList=()=>{
 return (dispatch)=>{
   axios.get('/api/headerList.json').then((res)=>{
    const data=res.data
     dispatch(changeList(data.data))
   }).catch(()=>{
     console.log('error')
   })
}
}
  • store/reducer.js
import {fromJS} from "immutable";
const defaultState=fromJS({
  list:[]
});

  case constants.CHANGE_LIST:
      return state.set('list', action.data)
  • header/index.js
 <SearchInfoList>
            {
              this.props.list.map((item) => {
                return <SearchInfoItem key={item}>{item}</SearchInfoItem>
              })
            }
 </SearchInfoList>

热门搜索换页功能实现

  • reducer.js
const defaultState = fromJS({
  page:1,
  totalPage:1
});
case constants.CHANGE_LIST:
      return state.merge({
        list: action.data,
        totalPage: action.totalPage
      });
  • actionCreators.js
const changeList=(data)=>({
  totalPage:Math.ceil(data.length / 10)
})

避免无意义的请求发送,提升组件性能

  • header/index.js
  const {list} = this.props;
return(
  <NavSearch className={focused ? 'focused' : ''}
                         onFocus={() => handleInputFocus(list)}
              />
     )
handleInputFocus(list) {
      (list.size ===0) && dispatch(actionCreators.getList())
      dispatch(actionCreators.searchFocus());
    },

8.项目实战: 首页开发

如何在 React 中使用路由功能

  • App.js
import {BrowserRouter, Route} from "react-router-dom";
   <Provider store={store}>
        <div>
          <Header/>
          <BrowserRouter>
            <div>
              {/*exact 完全跟 path 相等的*/}
              <Route path='/' exact render={() => <div>home</div>}/>
              <Route path='/detail' exact render={() => <div>detail</div>}/>
            </div>
          </BrowserRouter>
        </div>
     </Provider>

异步操作代码拆分优化

  • src/pages/home/store/constants.js
export const CHANGE_HOME_DATA='/home/CHANGE_HOME_DATA'
  • src/pages/home/store/actionCreators.js
import axios from "axios";
import * as constants from './constants'

const changeHomeData=(result)=>({
  type: constants.CHANGE_HOME_DATA,
  topicList: result.topicList,
  articleList: result.articleList,
  recommendList: result.recommendList
})

export const getHomeInfo=()=>{
  return (dispatch)=>{
    axios.get("/api/home.json").then(res => {
      const result = res.data.data;
      dispatch(changeHomeData(result));
    });
  }
}
  • src/pages/home/store/index.js
import reducer from "./reducer";
import * as actionCreators from './actionCreators'
export {reducer,actionCreators}
  • src/pages/home/index.js
import {connect} from "react-redux";
import {actionCreators} from "./store";
class Home extends Component {
componentDidMount() {
    this.props.changeHomeData();
  }
}

const mapDispatch = (dispatch) => ({
  changeHomeData() {
    const action = actionCreators.getHomeInfo();
    dispatch(action);
  }
});

export default connect(null, mapDispatch)(Home);

实现加载更多功能

  • src/pages/home/components/List.js
 <LoadMore onClick={getMoreList}>更多文字</LoadMore>
const mapDispatch = (dispatch) => ({
  getMoreList() {
    dispatch(actionCreators.getMoreList());
  }
});
export default connect(mapState, mapDispatch)(List);
  • src/pages/home/store/actionCreators.js
import {fromJS} from "immutable";

const addHomeList=(list)=>({
  type: constants.ADD_ARTICLE_LIST,
  list:fromJS(list)//fromJS,会把外层的数组和数组内部对象都转换为 immutable 类型
})

export const getMoreList = () => {
  return (dispatch) => {
    axios.get("/api/homeList.json").then(res => {
      const result = res.data.data;
      console.log(result);
      dispatch(addHomeList(result));
    });
  };
};
  • src/pages/home/store/reducer.js
 case constants.ADD_ARTICLE_LIST:
      return state.set('articleList',state.get('articleList').concat(action.list))

加载更多,根据后端传值页码不同内容不同

  • src/pages/home/components/List.js
 <LoadMore onClick={()=>getMoreList(page)}>更多文字</LoadMore>
 const mapState = (state) => ({
  list: state.getIn(["home", "articleList"]),
  page:state.getIn(["home","articlePage"])
});
const mapDispatch = (dispatch) => ({
  getMoreList(page) {
    dispatch(actionCreators.getMoreList(page));
  }
});
  • src/pages/home/store/actionCreators.js
const addHomeList=(list,nextPage)=>({
  type: constants.ADD_ARTICLE_LIST,
  list:fromJS(list),
  nextPage
})
export const getMoreList = (page) => {
  return (dispatch) => {
    axios.get("/api/homeList.json?page="+page).then(res => {
      const result = res.data.data;
      dispatch(addHomeList(result,page+1));
    });
  };
};
  • src/pages/home/store/reducer.js
const defaultState = fromJS({
  articlePage:1
});
 case constants.ADD_ARTICLE_LIST:
      return state.merge({
        'articleList':state.get('articleList').concat(action.list),
        'articlePage':action.nextPage
      })

首页性能优化

  • 首页的每一个组件基本上每个都调用的 connect 方法和 store 作了连接,只要 store 发生了改变,那么每个组件都会被重现渲染,每个组件的 render 函数都会被重新执行
 //判断只有和这组件相关的数据发生改变的时候,我才让组件的 render 函数执行,
  // 否则就 return false,来避免虚拟 dom 的比对,来提高性能,在每个组件都写这个,太麻烦了
  //PureComponent 纯组件,内在底层实现了一个 shouldComponentUpdate,所以不需要手写来做性能优化了
  shouldComponentUpdate() { }

9.项目实战:详情页面和登录功能开发

详情页面布局

喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
已稳定运行:3年255天3小时47分