IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React.js全家桶学习笔记吐血整理(下):高阶组件、context、Redux、React-redux(更新中...) -> 正文阅读

[JavaScript知识库]React.js全家桶学习笔记吐血整理(下):高阶组件、context、Redux、React-redux(更新中...)

一、高阶组件

?阶组件是?个函数(?不是组件),它接受?个组件作为 参数,返回?个新的组件。这个新的组件会使?你传给它的组件作为?组件。

一个简单的示范:

// 只要写 React.js 组件,那么就必须要引?这两个东西
import React, { Component } from 'react';

export default (WrappedComponent)=>{
  class OuterComponent extends Component{
    render(){
      return (
        <WrappedComponent />
      );
    }
  }
}

以上就是一个简单的高阶组件,传入组件WrappedComponent,然 后把传进?去的 WrappedComponent 渲染出来。

可以增加一些数据启动工作:

wrapWithLoadData.js

// 只要写 React.js 组件,那么就必须要引?这两个东西
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';

export default wrapWithLoadData=(WrappedComponent,name)=>{
  class OuterComponent extends Component{
    constructor(){
      super();
      this.state={
        data:null
      }
    }

    UNSAFE_componentWillMount(){
      let data=localStorage.getItem(name);
      this.setState({data});
    }


    render(){
      return (
        // 以props的形式将从localStorage获取到的data传给WrappedComponent组件
        <WrappedComponent data={this.state.data}/>
      );
    }
  };
  return OuterComponent;
}

另外新建一个文件,然后引入上面的高阶组件:

InputWithUsername.js

// 只要写 React.js 组件,那么就必须要引?这两个东西
import React, { Component } from 'react';
import './index.css';
import wrapWithLoadData from './wrapWithLoadData';

class InputWithUsername extends Component{
  render(){
    return (
      <input value={this.props.data}/>
    );
  }
}

InputWithUsername=wrapWithLoadData(InputWithUsername,'username');
export  default InputWithUsername;

首先将InputWithUsername组件和字符串username传给wrapWithLoadData这个高阶组件,然后高阶组件从localStorage中查到用户名信息,然后再以props的形式将从localStorage获取到的data传给InputWithUsername组件,并返回InputWithUsername组件。所以InputWithUsername在经过了高阶组件后,获取到了data。

我们可以根据自己的需求,设计具有不同包装效果的高阶组件,不需要改动被包装的组件,就可以很好地实现代码的复用。可以说,高阶组件具有充分的灵活性。

二、React.js 的 context

React.js规定,某个组件只要往??的 context ??放了 某些状态,这个组件之下的所有?组件都直接访问这个状态?不需要通过中间组件的 传递。?个组件的 context 只有它的?组件能够访问,它的?组件是不能访问到的。

先创建好组件树:

import React, { Component } from 'react';
import './index.css';
import ReactDOM from 'react-dom';

class Index extends Component { 
  render () { 
    return ( 
      <div> 
        <Header /> 
        <Main /> 
      </div> 
    ) 
  } 
}

class Header extends Component { 
  render () { 
    return ( 
      <div> 
        <h2>This is header</h2> 
        <Title /> 
      </div> 
    ) 
  } 
}

class Main extends Component { 
  render () { 
    return ( 
      <div> 
        <h2>This is main</h2> 
        <Content /> 
      </div> 
    ) 
  } 
}

class Title extends Component { 
  render () { 
    return ( 
      <h1>React.js 标题</h1> 
    ) 
  } 
}

class Content extends Component { 
  render () { 
    return ( 
      <div> 
        <h2>React.js 内容</h2> 
      </div> 
    )
  }
}

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

然后修改Index组件:

class Index extends Component { 
  // 1设置其孩子组件能访问到的context的类型
  static childContextTypes = { 
    themeColor: PropTypes.string 
  }

  constructor(){
    super();
    // 2 设置state.themeColor
    this.setState={
      themeColor:'red'
    }
  }

  // 3 将themeColor放进context里面,以供孩子组件们访问
  getChildContext () { 
    return { 
      themeColor: this.state.themeColor 
    } 
  }

  render () { 
    return ( 
      <div> 
        <Header /> 
        <Main /> 
      </div> 
    ) 
  } 
}

子组件Title获取context:

class Title extends Component { 
  // 1设置能访问到的context的类型,必须和父组件中定义的一致
  static childContextTypes = { 
    themeColor: PropTypes.string 
  }

  // 2 获取context
  render () { 
    return ( 
      <h1 style={{color:this.context.themeColor}}>React.js 标题</h1> 
    ) 
  } 
}

context 打破了组件和组件之间通过 props 传递数据的规范,极?地增强了组件之间的耦合性。因此不到必要的时刻不建议使用。

三、动手实现 Redux

Redux 和 React-redux 并不是同?个东西。Redux 是?种架构模式 (Flux 架构的?种变种),它不关注你到底?什么库,你可以把它应?到 React 和Vue,甚?跟 jQuery 结合都没有问题。? React-redux 就是把 Redux 这种架构模式 和 React.js 结合起来的?个库,就是 Redux 架构在 React.js 中的体现。

创建一个新的项目redux-demo。

修改index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body> 
    <div id='title'></div> 
    <div id='content'></div> 
  </body>
</html>

修改index.js:

const appState = { 
  title: { 
    text: 'React.js 教程', 
    color: 'red',
  },
  content: { 
    text: 'React.js 教程内容', 
    color: 'blue' 
  } 
}

function renderApp (appState) { 
  renderTitle(appState.title) 
  renderContent(appState.content) 
}

function renderTitle (title) { 
  const titleDOM = document.getElementById('title') 
  titleDOM.innerHTML = title.text 
  titleDOM.style.color = title.color 
}

function renderContent (content) { 
  const contentDOM = document.getElementById('content') 
  contentDOM.innerHTML = content.text 
  contentDOM.style.color = content.color 
}

renderApp(appState);

运行项目:

在这里插入图片描述

数据渲染成功。

这个项目存在着?个重?的隐患,我们渲染数据的时候,使?的是?个共享状态 appState ,每个?都可以修改它。?旦数据可以任意修改,所有对共享状态的操作都 是不可预料的,出现问题的时候 debug 起来就?常困难,因此我们应该避免使用全局变量。

我们可以学习 React.js 团队的做法,把事情搞复杂 ?些,提?数据修改的?槛:模块(组件)之间可以共享数据,也可以改数据。但是这个数据并不能直接改,只能执?允许的修改,?且这种修改必须?张旗?地告诉我。

1.dispatch函数

dispatch负责数据的修改,case限制了可以进行哪几种修改:

function dispatch (action) { 
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
    appState.title.text = action.text 
    break 
    case 'UPDATE_TITLE_COLOR': 
    appState.title.color = action.color 
    break 
    default: 
    break 
  } 
}

传入的参数action是一个JavaScript对象,type属性说明要修改的类型,其他属性则用来存储数据。

所有对数据的操作必须通过 dispatch 函数。它接受?个参数 action ,这个 action 是?个普通的 JavaScript 对象,??必须包含?个 type 字段来声明你到底想?什 么。 dispatch 在 swtich ??会识别这个 type 字段,能够识别出来的操作才会执 ?对 appState 的修改。

有了这个函数之后,我们只需要看dispatch函数在哪些地方被调用了就可以了,而不必担心我们的共享变量被随意地改来改去,一切尽在掌握之中。

示范:

const appState = { 
  title: { 
    text: 'React.js 教程', 
    color: 'red',
  },
  content: { 
    text: 'React.js 教程内容', 
    color: 'blue' 
  } 
}


function dispatch (action) { 
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
    appState.title.text = action.text 
    break 
    case 'UPDATE_TITLE_COLOR': 
    appState.title.color = action.color 
    break 
    default: 
    break 
  } 
}

function renderApp (appState) { 
  renderTitle(appState.title) 
  renderContent(appState.content) 
}

function renderTitle (title) { 
  const titleDOM = document.getElementById('title') 
  titleDOM.innerHTML = title.text 
  titleDOM.style.color = title.color 
}

function renderContent (content) { 
  const contentDOM = document.getElementById('content') 
  contentDOM.innerHTML = content.text 
  contentDOM.style.color = content.color 
}

renderApp(appState)//首次渲染
dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 高阶教程》' })// 修改标题文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'black' }) // 修改标题颜?
renderApp(appState)//再次渲染

在这里插入图片描述

2.抽取state和diapatch

在原来的基础上,我们修改一下代码,使得代码更加具有组织性和复用性:

let appState = { 
  title: { 
    text: 'React.js 教程', 
    color: 'red'
  },
  content: { 
    text: 'React.js 教程内容', 
    color: 'blue' 
  } 
}

// 将具体的修改数据的动作改名为stateChanger
function stateChanger (action) { 
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
      appState.title.text = action.text
      break 
    case 'UPDATE_TITLE_COLOR': 
      appState.title.color = action.color 
      break 
    default: 
      break 
  } 
}

// 这个函数很关键,应该抽取出来放在别的文件中,可以在任何需要的地方复用
// getState用来仅仅是获取数据
// dispatch用来对获取到的数据进行修改动作
function createStore(state,stateChanger){
  const getState=()=>state
  const dispatch=(action)=>stateChanger(state,action)
  return{getState,dispatch}
}

function renderApp (appState) { 
  renderTitle(appState.title) 
  renderContent(appState.content) 
}

function renderTitle (title) { 
  const titleDOM = document.getElementById('title') 
  titleDOM.innerHTML = title.text 
  titleDOM.style.color = title.color 
}

function renderContent (content) { 
  const contentDOM = document.getElementById('content') 
  contentDOM.innerHTML = content.text 
  contentDOM.style.color = content.color 
}

const store=createStore(appState,stateChanger)

renderApp(store.getState())//首次渲染
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 高阶教程》' })// 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'black' }) // 修改标题颜?
renderApp(store.getState())//再次渲染

3.监控数据变化

上面的代码有点问题,每次修改了数据之后我们都得手动再次渲染页面,所以下面我们来修改一下代码。

我们在 createStore ??定义了?个数组 listeners ,还有?个新的?法 subscribe ,可以通过 store.subscribe(listener) 的?式给 subscribe 传??个监听 函数,这个函数会被 push 到数组当中。

我们修改了 dispatch ,每次当它被调?的时候,除了会调? stateChanger 进?数据 的修改,还会遍历 listeners 数组??的函数,然后?个个地去调?。相当于我们可 以通过 subscribe 传?数据变化的监听函数,每当 dispatch 的时候,监听函数就会 被调?,这样我们就可以在每当数据变化时候进?重新渲染:

let appState = { 
  title: { 
    text: 'React.js 教程', 
    color: 'red'
  },
  content: { 
    text: 'React.js 教程内容', 
    color: 'blue' 
  } 
}

// 将具体的修改数据的动作改名为stateChanger
function stateChanger (action) { 
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
      appState.title.text = action.text
      break 
    case 'UPDATE_TITLE_COLOR': 
      appState.title.color = action.color 
      break 
    default: 
      break 
  } 
}

function createStore(state,stateChanger){
  const listeners=[]
  const subscrib=(listener)=>listeners.push(listener)
  const getState=()=>state
  const dispatch=(action)=>{
    stateChanger(state,action)
    listeners.forEach((listener)=>listener())
  }
  return{getState,dispatch,subscrib}
}

function renderApp (appState) { 
  renderTitle(appState.title) 
  renderContent(appState.content) 
}

function renderTitle (title) { 
  const titleDOM = document.getElementById('title') 
  titleDOM.innerHTML = title.text 
  titleDOM.style.color = title.color 
}

function renderContent (content) { 
  const contentDOM = document.getElementById('content') 
  contentDOM.innerHTML = content.text 
  contentDOM.style.color = content.color 
}

const store=createStore(appState,stateChanger)
// 只需要在这里写一次,后面无论修改多少次都会自动渲染
store.subscrib(()=>renderApp(store.getState()))

store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 高阶教程》' })// 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'black' }) // 修改标题颜?

修改一次数据,自动渲染多个页面也是可以的:

const store = createStore(appState, stateChanger) 
store.subscribe(() => renderApp(store.getState())) 
store.subscribe(() => renderApp2(store.getState())) 
store.subscribe(() => renderApp3(store.getState()))

4.纯函数

纯函数的两个特征:
  • 只依赖形参,不依赖外部变量。
  • 不对外部产生影响,比如修改外部变量的值。

5.通过共享结构的对象来优化渲染性能

上面的代码虽然能实现我们需要的功能,但是每次一个组件稍有变动就把所有组件都重新渲染,当组件越来越多的时候,将会出现严重的性能问题,所以我们应该想办法只渲染有改动的组件,对于没有改动的组件不重新渲染。

我们很容易想到的就是通过判断newAppState == oldAppState是否为true的方式来决定是否重新渲染父组件,通过判断newAppState.title == oldAppState.title是否为true的方式来决定是否重新渲染Title组件,通过判断newAppState.content == oldAppState.content是否为true的方式来决定是否重新渲染Content组件。但是,如果newAppState是由oldAppState对象复制得来的,虽然其中的某些信息发生了变化,比如title的text变化了,content的color变化了,但事实上newAppState和oldAppState指向的仍然是同一个对象,newAppState.title和oldAppState.title,newAppState.content和oldAppState.content指向的也都是同一个对象。所以哪怕其中的某些数据被改动了,但上面几个相等判断始终为true,无法作为我们判断是否应该重新渲染组件的依据。

这一切都归功于JavaScript语言的特性,当我们试图复制一个对象的时候,只会复制这个变量,事实上这两个变量指向的仍然是同一个对象。

所以我们需要学习一下ES6的浅复制。

下面的两行代码表示将obj的每个属性都复制一份,生成一个新的对象,然后由obj2指向这个新对象。这样就得到了两个完全一样却并不相等的对象:

const obj = { a: 1, b: 2} 
const obj2 = { ...obj } // => { a: 1, b: 2 }

还可以对原来的对象进行部分数据覆盖,以及扩展:

const obj = { a: 1, b: 2} 
const obj2 = { ...obj, b: 3, c: 4} // => { a: 1, b: 3, c: 4 },覆盖了 b,新增了 c

我们可以把上面的语法应用到我们的项目代码中去:

index.js


function createStore(state,stateChanger){
  const listeners=[]
  const subscribe=(listener)=>listeners.push(listener)//每次调用传入一个listener
  const getState=()=>state
  const dispatch=(action)=>{
    state=stateChanger(state,action)//覆盖state
    listeners.forEach((listener)=>listener())
  }
  return{getState,dispatch,subscribe}
}

function renderApp (newAppState,oldAppState={}) { 
  if(newAppState === oldAppState)return;
  renderTitle(newAppState.title,oldAppState.title);
  renderContent(newAppState.content,oldAppState.content);
  console.log("renderApp...")
}

function renderTitle (newTitle,oldTitle={}) { 
  if(newTitle===oldTitle)return;
  const titleDOM = document.getElementById('title') 
  titleDOM.innerHTML = newTitle.text 
  titleDOM.style.color = newTitle.color 
  console.log("renderTitle...")
}

function renderContent (newContent,oldContent={}) { 
  if(newContent===oldContent)return;
  const contentDOM = document.getElementById('content') 
  contentDOM.innerHTML = newContent.text 
  contentDOM.style.color = newContent.color 
  console.log("renderContent...")
}

function stateChanger(state,action) { 
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
    return{//构建新的state对象并返回
      ...state,
      title:{//content还是指向同一个,但title重新复制并修改了text
        ...state.title,
        text:action.text
      }
    }
    case 'UPDATE_TITLE_COLOR': 
      return{//构建新的state对象并返回
        ...state,
        title:{//content还是指向同一个,但title重新复制并修改了color
          ...state.title,
          color:action.color
        }
      }
    default: 
      return state
  } 
}

let appState = { 
  title: { 
    text: 'React.js 教程', 
    color: 'red'
  },
  content: { 
    text: 'React.js 教程内容', 
    color: 'blue' 
  } 
}

const store=createStore(appState,stateChanger)
let oldState=store.getState()

store.subscribe(()=>{
  const newState=store.getState()
  renderApp(newState,oldState)
  oldState=newState
})

renderApp(store.getState())
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 高阶教程》' })// 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'black' }) // 修改标题颜?


在两次修改数据后,仅仅是重新渲染了修改的组件:

在这里插入图片描述

6.reducer

我们把appState和createStore合并到一起,并把形参stateChanger改为叫reducer:

得到了最终版的createStore方法:

function createStore(reducer){
  let state=null
  const listeners=[]
  const subscribe=(listener)=>listeners.push(listener)//每次调用传入一个listener
  const getState=()=>state
  const dispatch=(action)=>{
    state=reducer(state,action)//覆盖state
    listeners.forEach((listener)=>listener())
  }
  dispatch({});//初始化state
  return{getState,dispatch,subscribe}
}

再来修改下stateChanger:

function titleReducer(state,action) { 
  // 如果没传入state,就返回默认数据,相当于初始化state
  if(!state){
    return ({ 
      title: { 
        text: 'React.js 教程', 
        color: 'red'
      },
      content: { 
        text: 'React.js 教程内容', 
        color: 'blue' 
      } 
    })    
  }
  // 传入了state,则对state进行修改
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
    return{//构建新的state对象并返回
      ...state,
      title:{//content还是指向同一个,但title重新复制并修改了text
        ...state.title,
        text:action.text
      }
    }
    case 'UPDATE_TITLE_COLOR': 
      return{//构建新的state对象并返回
        ...state,
        title:{//content还是指向同一个,但title重新复制并修改了color
          ...state.title,
          color:action.color
        }
      }
    default: 
      return state
  } 
}

createStore 接受?个叫 reducer 的函数作为参数,这个函数规定是?个纯函数,它 接受两个参数,?个是 state ,?个是 action 。 如果没有传? state 或者 state 是 null ,那么它就会返回?个初始化的数据。如 果有传? state 的话,就会根据 action 来“修改“数据,然后产??个新的对象返回。如果它不能识别你的 action ,它就不会产?新的数据,?是(在 default 内部)把 state 原封不动地返回。

下面用代码来示范下用法:


function renderApp (newAppState,oldAppState={}) { 
  if(newAppState === oldAppState)return;
  renderTitle(newAppState.title,oldAppState.title);
  renderContent(newAppState.content,oldAppState.content);
  console.log("renderApp...")
}

function renderTitle (newTitle,oldTitle={}) { 
  if(newTitle===oldTitle)return;
  const titleDOM = document.getElementById('title') 
  titleDOM.innerHTML = newTitle.text 
  titleDOM.style.color = newTitle.color 
  console.log("renderTitle...")
}

function renderContent (newContent,oldContent={}) { 
  if(newContent===oldContent)return;
  const contentDOM = document.getElementById('content') 
  contentDOM.innerHTML = newContent.text 
  contentDOM.style.color = newContent.color 
  console.log("renderContent...")
}

function createStore(reducer){
  let state=null
  const listeners=[]
  const subscribe=(listener)=>listeners.push(listener)//每次调用传入一个listener
  const getState=()=>state
  const dispatch=(action)=>{
    state=reducer(state,action)//覆盖state
    listeners.forEach((listener)=>listener())
  }
  dispatch({});//初始化state
  return{getState,dispatch,subscribe}
}

function titleReducer(state,action) { 
  // 如果没传入state,就返回默认数据,相当于初始化state
  if(!state){
    return ({ 
      title: { 
        text: 'React.js 教程', 
        color: 'red'
      },
      content: { 
        text: 'React.js 教程内容', 
        color: 'blue' 
      } 
    })    
  }
  // 传入了state,则对state进行修改
  switch (action.type) { 
    case 'UPDATE_TITLE_TEXT': 
    return{//构建新的state对象并返回
      ...state,
      title:{//content还是指向同一个,但title重新复制并修改了text
        ...state.title,
        text:action.text
      }
    }
    case 'UPDATE_TITLE_COLOR': 
      return{//构建新的state对象并返回
        ...state,
        title:{//content还是指向同一个,但title重新复制并修改了color
          ...state.title,
          color:action.color
        }
      }
    default: 
      return state
  } 
}

const store=createStore(titleReducer)
// 获取原始数据
let oldState=store.getState()

store.subscribe(()=>{
  const newState=store.getState()
  renderApp(newState,oldState)
  oldState=newState
})

renderApp(store.getState())
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 高阶教程》' })// 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'black' }) // 修改标题颜?


在这里插入图片描述

四、动手实现 React-redux(未完待续)

这节开始我们来看看如何把 Redux 和 React.js 结合起来。

在学状态提升的时候,我们发现?个状态可能被多个组件依赖或者影响,? React.js 并没有提供好的解决 ?案,我们只能把状态提升到依赖或者影响这个状态的所有组件的公共?组件上,我 们把这种?为叫做状态提升。但是需求不停变化,共享状态没完没了地提升也不是办 法。

后来我们学到了context,我们可?把共享状态放到?组件的context 上,这个?组件下所有的组件都可以从 context 中直接获取到状态?不需要 ?层层地进?传递了。但是直接从 context ??存放、获取数据增强了组件的耦合性;并且所有组件都可以修改 context ??的状态就像谁都可以修改共享状态?样, 导致程序运?的不可预料。

现在,我们不如把context 和 store 结合起来?毕竟 store 的数据不是谁都能 修改,?是约定只能通过 dispatch 来进?修改,这样的话每个组件既可以去context ??获取 store 从?获取状态,?不?担?它们乱改数据了。

1.明确需求,新建项目theme-color

在这里插入图片描述

我们这一章做一个小实例,Header 和 Content 的组件的?本内容会随着主题?的变化?变化,? Content 下 的?组件 ThemeSwitch 有两个按钮,可以切换红?和蓝?两种主题,按钮的颜?也会 随着主题?的变化?变化。

我们新建一个项目theme-color,运行之后在VS code中打开它。在src下新增三个文件:Header.js 、 Content.js 、 ThemeSwitch.js。

修改Header.js:

import react,{Component} from 'react'
import PropTypes from 'prop-types'

class Header extends Component{
    render(){
        return (
            <h1>React.js 教程</h1>
        )
    }
}

export default Header

修改ThemeSwitch.js:

import react,{Component} from 'react'
import PropTypes from 'prop-types'

class ThemeSwitch extends Component { 
    render () { 
        return ( 
        <div> 
            <button>Red</button> 
            <button>Blue</button> 
        </div> 
        ) 
    } 
}

export default ThemeSwitch

修改Content.js:

import React, { Component } from 'react' 
import PropTypes from 'prop-types' 
import ThemeSwitch from './ThemeSwitch' 

class Content extends Component { 
    render () { 
        return ( 
            <div> 
                <p>React.js 教程内容</p> 
                <ThemeSwitch /> 
            </div> 
        ) 
    } 
}

export default Content

修改index.js:

import React, { Component } from 'react' 
import PropTypes from 'prop-types' 
import ReactDOM from 'react-dom' 
import Header from './Header' 
import Content from './Content' 
import './index.css'

class Index extends Component { 
  render () { 
    return ( 
      <div> 
        <Header /> 
        <Content /> 
      </div> 
    ) 
  } 
}

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

在这里插入图片描述

2.结合store和context

我们先修改index.js,设置好createStore、reducer,并把store放进context里面。

index.js

import React, { Component } from 'react' 
import PropTypes from 'prop-types' 
import ReactDOM from 'react-dom' 
import Header from './Header' 
import Content from './Content' 
import './index.css'

class Index extends Component { 

  // 4 定义context的类型
  static childContextTypes={
    store:PropTypes.object
  }

  // 5 把state放进context里,供子组件访问state
  getChildContext(){
    return {store}
  }

  render () { 
    return ( 
      <div> 
        <Header /> 
        <Content /> 
      </div> 
    ) 
  } 
}

// 1 定义createStore函数
function createStore (reducer) { 
  let state = null 
  const listeners = [] 
  const subscribe = (listener) => listeners.push(listener) 
  const getState = () => state 
  const dispatch = (action) => { 
    state = reducer(state, action) 
    listeners.forEach((listener) => listener()) 
  }
  dispatch({}) // 初始化 state 
  return { getState, dispatch, subscribe } 
}

// 2 定义主题色相关的reducer
const themeReducer=(state,action)=>{
  if(!state)return {
    themeColor:'red'
  }
  switch(action.type){
    case 'CHANGE_COLOR':
      return {...state,themeColor:action.themeColor}
    default:
      return state
  }
}

// 3 调用createStore将返回初始化的state
const store=createStore(themeReducer)

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

然后修改几个子组件,需要添加的代码部分是一模一样的:

Header.js

import react,{Component} from 'react'
import PropTypes from 'prop-types'

class Header extends Component{
    // 1 定义context接收类型
    static contextTypes={
        store:PropTypes.object
    }

    constructor(){
        super()
        // 2 初始化state
        this.state={themeColor:''}
    }

    // 3 在挂载之前更新主题色
    componentWillMount(){
        this._updateThemeColor()
    }

    // 4 从context中取出主题色,并放进state
    _updateThemeColor(){
        const store=this.context.store
        const state=store.getState()
        this.setState({
            themeColor:state.themeColor
        })
    }

    // 5 访问state获取主题色
    render(){
        return (
            <h1 style={{color:this.state.themeColor}}>React.js 教程</h1>
        )
    }
}

export default Header

Content.js

import React, { Component } from 'react' 
import PropTypes from 'prop-types' 
import ThemeSwitch from './ThemeSwitch' 

class Content extends Component { 
    static contextTypes={
        store:PropTypes.object
    }

    constructor(){
        super()
        this.state={themeColor:''}
    }

    componentWillMount(){
        this._updateThemeColor()
    }

    _updateThemeColor(){
        const store=this.context.store
        const state=store.getState()
        this.setState({
            themeColor:state.themeColor
        })
    }

    render () { 
        return ( 
            <div> 
                <p style={{color:this.state.themeColor}}>React.js 教程内容</p> 
                <ThemeSwitch /> 
            </div> 
        ) 
    } 
}

export default Content

ThemeSwitch.js

import react,{Component} from 'react'
import PropTypes from 'prop-types'

class ThemeSwitch extends Component { 
    static contextTypes={
        store:PropTypes.object
    }

    constructor(){
        super()
        this.state={themeColor:''}
    }

    componentWillMount(){
        this._updateThemeColor()
    }

    _updateThemeColor(){
        const store=this.context.store
        const state=store.getState()
        this.setState({
            themeColor:state.themeColor
        })
    }

    render () { 
        return ( 
            <div> 
                <button style={{color:this.state.themeColor}}>Red</button> 
                <button style={{color:this.state.themeColor}}>Blue</button> 
            </div> 
        ) 
    } 
}

export default ThemeSwitch

主题色生效了:

在这里插入图片描述

下面来实现一键更换主题色

修改ThemeSwitch.js

import react,{Component} from 'react'
import PropTypes from 'prop-types'

class ThemeSwitch extends Component { 
    static contextTypes={
        store:PropTypes.object
    }

    constructor(){
        super()
        this.state={themeColor:''}
    }

    componentWillMount(){
        this._updateThemeColor()
    }

    _updateThemeColor(){
        const store=this.context.store
        const state=store.getState()
        this.setState({
            themeColor:state.themeColor
        })
    }

    // 2 访问store并调用dispatch,然后传入action的type和themecolor
    handleChangeColor(color){
        const store=this.context.store
        store.dispatch({
            type:'CHANGE_COLOR',
            themeColor:color
        })
    }

    render () { 
        return ( 
            <div> 
                {/* 1 绑定onClick事件,并向函数传入颜色参数 */}
                <button style={{color:this.state.themeColor}}
                 onClick={this.handleChangeColor.bind(this,'red')}>Red</button> 
                <button style={{color:this.state.themeColor}} 
                 onClick={this.handleChangeColor.bind(this,'blue')}>Blue</button> 
            </div> 
        ) 
    } 
}

export default ThemeSwitch

然后将Header、Content、ThemeSwitch组件componentWillMount方法都修改了:

 componentWillMount(){
        const store=this.context.store
        this._updateThemeColor()
        store.subscribe(()=>this._updateThemeColor())
    }

这样每次切换了主题色之后几个组件都会重新渲染组件:

在这里插入图片描述

在这里插入图片描述

3.connect和mapStateToProps

上面的代码有两个问题:

  • 注意到了吗,每个组件从取出context到渲染页面的过程逻辑和代码几乎都是一样的,所以其实我们写了大量重复无用的代码。这个问题可以通过前面我们学过的高阶组件来解决,只需要把每个组件传进去,再返回包装之后的组件就可以了,
  • 另一个问题是,每一个组件对context都依赖过强,必须保证实现了context和store才能复用这个组件,所以其实我们的组件复用性很差。因此我们应该写组件的时候应该尽可能地让组件不依赖外界数据,也不对外界产生副作用(这种组件又称为Dumb组件),与context打交道的工作就交给高阶组件来完成。

我们把这里提到的高阶组件称为connect,因为它把 Dumb 组件和 context 连接 (connect)起来了。总之,我们需要?阶组件帮助我们从 context 取数据,我们 也需要写 Dumb 组件帮助我们提?组件的复?性。所以我们尽量多地写 Dumb 组件,然后??阶组件把它们包装?层,?阶组件和 context 打交道,把??数据取出来通 过 props 传给 Dumb 组件。

在这里插入图片描述

在src下面新建一个模块react-redux.js,用来存放connect高阶组件:

react-redux.js
业务逻辑简单来说就是:把接收到的context以props的形式传给dumb组件

import React, {Component} from 'react';
import PropTypes from 'prop-types';

export const connect=(mapStateToProps)=>(WrappedComponent)=>{
    class Connect extends Component{
        // 用来接收context
        static contextType={
            store:PropTypes.object
        }

        render(){
            // 把接收到的context中的state用mapStateToProps函数映射成props
            // 并且用{...stateProps}语法挨个以props的形式传给dumb组件
            const store=this.context.store
            let stateProps=mapStateToProps(store.getState())
            return <WrappedComponent {...stateProps}/>
        }
    }

    return Connect
}

关于mapStateToProps函数,不同dumb组件有不同的定义,因为它们需要获取的props很可能各不相同。

Header.js
业务逻辑简单来说就是,从connect中按照需要(由mapStateToProps规定)获取props,并渲染到组件中去。

import react,{Component} from 'react'
import PropTypes from 'prop-types'
import connect from './react-redux'

class Header extends Component{
    // 1 定义props接收类型
    static propsTypes={
        themeColor:PropTypes.string
    }

    render(){
        return (
            <h1 style={{color:this.props.themeColor}}>React.js 教程</h1>
        )
    }
}

// 2 传入的state中可能会有很多数据,只取需要的
const mapStateToProps=(state)=>{
    return {
        themeColor:state.themeColor
    }
}


Header=connect(mapStateToProps)(Header)

export default Header

Content.js修改同上:

import React, { Component } from 'react' 
import PropTypes from 'prop-types' 
import ThemeSwitch from './ThemeSwitch' 
import { connect } from './react-redux'

class Content extends Component { 
    static propTypes={
        themeColor:PropTypes.string
    }

    render () { 
        return ( 
            <div> 
                <p style={{color:this.props.themeColor}}>React.js 教程内容</p> 
                <ThemeSwitch /> 
            </div> 
        ) 
    } 
}

const mapStateToProps=(state)=>{
    return {
        themeColor:state.themeColor
    }
}

Content=connect(mapStateToProps)(Content)

export default Content

修改connect
给 connect 的?阶组件增加监听数据变化重新渲染的逻辑。

import React, {Component} from 'react';
import PropTypes from 'prop-types';

export const connect=(mapStateToProps)=>(WrappedComponent)=>{
    class Connect extends Component{
        static contextType={
            store:PropTypes.object
        }

        constructor(){
            super()
            this.state={allProps:{}}
        }

        componentWillMount(){
            const store=this.context.store
            this._updateProps()
            store.subscribe(()=>this._updateProps)
        }

        _updateProps(){
            const store=this.context.store
            //把context中的state还有Connect组件的props合并起来传给dumb组件
            //mapStateToProps需要设置能接受两个参数,这样dumb组件可以选择的数据源就更多了
            let stateProps=mapStateToProps(store.getState(),this.props)
            this.setState({
                allProps:{
                    ...stateProps,
                    ...this.props
                }
            })
        }

        render(){
            return <WrappedComponent {...this.state.allProps}/>
        }
    }

    return Connect
}

(这一章未完待续)

五、Smart 组件 vs Dumb 组件

只会接受 props 并且渲染确定结果的组件我们把它叫做 Dumb 组 件,这种组件只关??件事情 —— 根据 props 进?渲染。

还有?种组件,它们?常 聪明(smart),城府很深精通算计,我们叫它们 Smart 组件。它们专?做数据相关 的应?逻辑,和各种数据打交道、和 Ajax 打交道,然后把数据通过 props 传递给Dumb,它们带领着 Dumb 组件完成了复杂的应?程序逻辑。

当我们拿到?个需求开始划分组件的时候,要认真考虑每个被划分成组件的单元到底 会不会被复?。如果这个组件可能会在多处被使?到,那么我们就把它做成 Dumb 组件。

Smart 组件不?考虑太多复?性问题,它们就是?来执?特定应?逻辑的。Smart 组 件可能组合了 Smart 组件和 Dumb 组件;但是 Dumb 组件尽量不要依赖 Smart 组 件。因为 Dumb 组件?的之?是为了复?,?旦它引?了 Smart 组件就相当于带?了 ?堆应?逻辑,导致它?法??,所以尽量不要?这种事情。?旦?个可复?的 Dumb组件之下引?了?个 Smart 组件,就相当于污染了这个 Dumb 组件树。如果?个组件 是 Dumb 的,那么它的?组件们都应该是 Dumb 的才对。

我们把 Smart 和 Dumb 组件分开到两个不同的?录,不再在Dumb 组件内部进? connect ,在 src/ ?录下新建两个?件夹 components/ 和 containers/,所有的 Dumb 组件都放在 components/ ?录下,所有的 Smart 的组件都 放在 containers/ ?录下,这是?种约定俗成的规则。

六、实战:评论功能3.0

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-02 10:43:22  更:2021-08-02 10:46:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/22 1:42:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码