React 详解【 JSX + 组件 + prop、state、ref + 事件 + 生命周期 + React Ajax + React Router路由 + Redux】

React 是什么

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。

安装 React

在安装 React 之前需要安装 nodejs,之后使用 node 中的 npm 命令来搭建 Reate 运行环境

安装步骤:

  1. 安装 nodejs

  2. 安装淘宝定制的 cnpm 命令,使用 cnpm 代替 npm,速度会快很多:
    $ npm install -g cnpm --registry=https://registry.npm.taobao.org
    $ npm config set registry https://registry.npm.taobao.org
    使用 $ cnpm install [name] 来安装你想要的模块

  3. 使用 create-react-app 快速构建 React 开发环境:
    $ cnpm install -g create-react-app

    $ create-react-app my-app 创建 create 项目
    $ cd my-app/
    $ npm start

访问 http://localhost:3000/ 即可看到效果

React 中的元素渲染

例如:

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>

<body>

<div id="example"></div>

<script type="text/babel">
    const element = <h1>Hello, world!</h1>;

    ReactDOM.render(
        element,
        document.getElementById('example')
    );
</script>
</body>

</html>

React 通过方法ReactDOM.render()将元素 element 渲染到 id 为 example 的标签内,当然也可同时定义多个 div 进行多次渲染,例如:

<div id="example" style="width: 100px; height: 100px; background-color: antiquewhite;"></div>
<div id="example2" style="width: 100px; height: 100px; background-color:aqua"></div>
<div id="example3" style="width: 100px; height: 100px; background-color:aquamarine"></div>

<script type="text/babel">
    const element = <h1>Hello, world!</h1>;
    const element2 = <h2>Hello, world!</h2>;
    const element3 = <h3>Hello, world!</h3>;

    ReactDOM.render(
        element,
        document.getElementById('example')
    );
    ReactDOM.render(
        element2,
        document.getElementById('example2')
    );
    ReactDOM.render(
        element3,
        document.getElementById('example3')
    );
</script>

React JSX

在 React 中使用 JSX 来替代常规的 JavaScript,例如代码const element = <h1>Hello, world!</h1>;就是 JSX 语言,像是传统 js 与 html 的结合,JSX 的作用就是声明 React 当中的元素,例如用变量 element 指向声明的元素,之后要使用该元素的话,直接用 element 就好了。

  • JSX 是 JavaScript 的语法扩展。 推荐在 React 中使用 JSX 来描述用户界面。

  • JSX 是在 JavaScript 内部实现的。

  • JSX 定义的元素是普通的对象,React DOM 可以确保浏览器 DOM 的数据内容与 React 元素保持一致。

使用 JSX 的好处:

  1. JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  2. 它是类型安全的,在编译过程中就能发现错误。
  3. 使用 JSX 编写模板更加简单快速。

注意:由于 JSX 就是 JavaScript,一些标识符像 classfor 不建议作为属性名。作为替代,React DOM 使用 classNamehtmlFor 来做对应的属性,这是因为 classfor 是 JavaScript 的保留字。

使用 React JSX

  1. JSX 中可定义多个元素,例如:
const element = (
  <div>
    <h1>Hello</h1>
    <h2>Good to see you</h2>
  </div>
);
  1. 在 JSX 中嵌入 {} 表达式,可在大括号 {} 内放置任何有效的 js 表达式,例如算式、user.userName等等
<body>
<div id="example"></div>

<script type="text/babel">
    const user = { userName: "张三", age: "18" };
    const element = <h1>Hello, {user.userName}</h1>;

    ReactDOM.render(
        element,
        document.getElementById('example')
    );
</script>
</body>

效果为:
在这里插入图片描述

React 组件

Reate 的组件相当于 js 中的函数

  1. 定义方式1:看下面代码块,React 定义了一个组件,该组件接收一个prarm参数,返回一个元素。这类组件被称为“函数组件”,因为它本质上就是 js函数。
function ReturnMessage(prarm) {
    return <h3>hello, {prarm.name}</h3>;
}
  1. 定义方式2:下面代码块使用 ES6 的 class 来定义组件,使用 class 来定义组件会有额外特性,之后介绍。
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

渲染组件

React 元素也可以是用户自定义的组件:const element3 = <ReturnMessage name="张三" />;,看以下代码块:

<body>
<div id="example3"></div>

<script type="text/babel">
    function ReturnMessage(props) {
    	return <h3>hello, {props.name}</h3>;
	}

    const element3 = <ReturnMessage name="张三" />;

    ReactDOM.render(
        element3,
        document.getElementById('example3')
    );
</script>
</body>

当 React 元素(JSX)为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。这段代码会在页面上渲染 hello, 张三

注意,原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头,比如 ReturnMessage不能写成 returnMessage。除此之外还需要注意组件类只能包含一个顶层标签,否则也会报错。

复合组件

看以下代码,复合组件就像层层方法调用,在 App() 中调用了 Name() 与 Age() 组件,在 ReactDOM.render() 中调用 App() 组件。

<body>
<div id="example2" style="width: 100px; height: 100px; background-color:aqua"></div>

<script type="text/babel">
    const element2 = <h2>Hello, world!</h2>;
    
    ReactDOM.render(
        <App />,
        document.getElementById('example2')
    );

    function App() {
        return (
            <div>
                <Name name="张三" />
                <Age age="18" />
            </div>
        );
    }
    
	function Name(props) {
        return <h6>名称:{props.name}</h6>;
    }
    function Age(props) {
        return <h6>年龄:{props.age}</h6>;
    }
</script>
</body>

效果:
在这里插入图片描述

组件的三大属性(state、props、refs)

state

  1. state 是组件对象最重要的属性,值是对象(可以包含多个数据)

  2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件),state 的值一旦被改变会触发该组件的重新渲染

使用 state
  1. 初始化状态:
  constructor (props) {
    super(props)
    this.state = {
      stateProp1 : value1,
      stateProp2 : value2
    }
  }
  1. 读取某个状态值:this.state.状态值名

  2. 更新状态

  this.setState({
    stateProp1 : value1,
    stateProp2 : value2
  })
使用 state 的小案例

在这里插入图片描述
点击按钮,左边的数字 + 1:

<body>
<div id="example2"></div>

//点击按钮,数字+1
class MyComponent2 extends React.Component {
    constructor(props) {
        super(props);
        this.click = this.click.bind(this);
        //初始化 state
        this.state = { count: 0 }
    }

    click(e) {
        var i = this.state.count + 1;
        //修改 state
        this.setState({
            count: i
        })
    }

    render() {
        return (
            <div>
                <span>{this.state.count}</span> 
                <button type="button" onClick={this.click}>+1按钮</button>
            </div>
        )
    }
}

ReactDOM.render(<MyComponent2 />, document.getElementById("example2"));
</script>
</body>

props

  1. 每个组件对象都会有 props(properties的简写) 属性

  2. 组件标签的所有属性都保存在 props 中

作用
  1. 通过标签属性从组件外向组件内传递变化的数据

  2. 注意: 组件内部不要修改 props 数据,props 具有只读性,组件无论是使用函数声明或是 class 声明,都决不能修改自身的 props。例如以下这个 sum 函数:

function sum(a, b) {
  return a + b;
}

这样的函数被称为纯函数,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。所有组件都必须像纯函数一样保护它们的 props 不被更改

使用 props

首先构建一个组件 Person:

function Person(props) {
    return (
        <ul>
            <li>name:{props.name}</li>
            <li>age:{props.age}</li>
            <li>gender:{props.gender}</li>
        </ul>
    )
}

要使用ReactDOM.render()将一个 person 对象传入 Person 组件中,返回相应元素;age、gender 属性要设置默认,name、age 属性的类型要被限制,以下是使用方式:

  1. 内部读取某个属性值this.props.propertyName
  2. 对 props 中的属性值进行类型限制和必要性限制:
//该方式写在组件类的外面
Person.propTypes = {
	name: React.PropTypes.string.isRequired,
	age: React.PropTypes.number.isRequired
}
//写在组件类里面的形式,static 表示给组件类指定属性
static propTypes = {
    name: React.PropTypes.string.isRequired,
	age: React.PropTypes.number.isRequired
}
  1. 对 props 设置默认属性值:
Person.defaultProps = {
    age: 20,
    gender: "男"
};
  1. 组件类的构造函数:
constructor (props) {
	super(props)
	console.log(props) // 查看所有属性
}
  1. 扩展属性,将对象的所有属性通过props传递:
const p1 = {
    name: "tom",
    age: 10,
    gender: "女"
};
// ReactDOM.render(<Person name={p1.name} age={p1.age} />,
// document.getElementById("example"));

ReactDOM.render(<PersonClass {...p1} />, document.getElementById("example"));

效果:
在这里插入图片描述

refs

  1. 组件内的标签都可以定义 ref 属性来标识自己,例如:<input type="text" ref={input => this.msgInput = input}/>

  2. 在组件中可以通过this.msgInput来得到对应的真实DOM元素

  3. 可通过 ref 获取组件内容特定标签对象,进行读取其相关数据

refs 使用的小案例

新建一个 click 事件,在点击按钮时弹出文本框中的内容:

<div id="example"></div>

<script type="text/babel">

    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            this.click = this.click.bind(this);
        }

        click(e) {
            //e.target 得到发生事件的DOM元素对象
            //alert(e.target);
            alert(this.refs.first.value);
        }

        render() {
            return (
                <div>
                    <input type="text" ref="first" />
                    <button type="button" onClick={this.click}>按钮</button>
                </div>
            )
        }
    }

    ReactDOM.render(<MyComponent />, document.getElementById("example"));
</script>
注意:
  1. 组件内置的方法中的 this 为组件对象
  2. 在组件类中自定义的方法中 this 为 null
  3. 强制绑定 this 通过函数对象的 bind()
  4. 箭头函数(ES6模块化编码时才能使用)

组件内的事件

通过元素的 onXXX 属性指定一个事件处理函数,例如:onClick={this.click},在组件中定义一个 click 函数。

事件函数的使用

//这种事件函数需要在组件构造当中进行绑定,例如 this.click = this.click.bind(this);
click(event) {
  	//操作...
}

//下列定义事件函数的方法不需在构造中 bind()
click2 = (event) => {
    //操作...
}
//如果子组件要调用父组件函数的话,父组件需要将函数传给自组件,例如:
//这是父组件的 operate() 与 render() 函数
operate = (`可以定义参数,也可以空着`) => {
    //操作...
}

render() {
    return (
        <div>
            <Son operate={this.operate} />
        </div>
    )
}

//子组件在调用父组件的函数前,需要规定参数类型,例如:
static propTypes = {
    operate: PropTypes.func.isRequired
}

//子组件调用父组件函数
click = () => {
    //从props中获取事件函数
    const {operate} = this.props;
	operate(`定义了参数就传入参数`);
}

render() {
    return (
        <div>
            <a onClick={this.click}>操作</a>
        </div>
    )
}

组件的生命周期

  1. 组件对象从创建到死亡它会经历特定的生命周期阶段

  2. React组件对象包含一系列的勾子函数(生命周期回调函数),在生命周期特定时刻回调

  3. 在定义组件时可以重写特定的生命周期回调函数,让不同的函数做特定的工作

组件三个生命周期状态

  1. Mount:将组件插入真实 DOM

  2. Update:组件被重新渲染

  3. Unmount:组件被移出真实 DOM

生命周期方法与流程

  • 第一次初始化渲染显示: ReactDOM.render()
  1. constructor(): 创建对象初始化构造器,初始化 state

  2. componentWillMount() : 组件将被插入 DOM 时触发

  3. render() : 组件插入虚拟 DOM 时触发,也就是开始渲染

  4. componentDidMount() : 组件已经插入到 DOM 后触发

  • 每次更新状态时state: this.setSate()
  1. componentWillUpdate() : 组件在更新数据前触发

  2. render() : 更新(重新渲染)

  3. componentDidUpdate() : 组件更新后触发

  • 移除组件: ReactDOM.unmountComponentAtNode(containerDom)
  1. componentWillUnmount() : 组件将要从 DOM 中移除触发

重要的生命周期方法

  1. render(): 初始化渲染或更新渲染触发

  2. componentDidMount(): 开启监听, 发送 ajax 请求

  3. componentWillUnmount(): 做一些收尾工作, 如: 清理定时器

  4. componentWillReceiveProps():

虚拟DOM 与 DOM Diff 算法

虚拟 DOM 指 JS 对象,也就是一个组件中 render() 方法中的 html 元素,通过ReactDOM.render()方法将虚拟 DOM 转化为真实的 DOM

DOM Diff 算法:

  1. 当页面初始化完成,所有组件都渲染好之后
  2. 在后续操作中,某一个组件的 state 被改变
  3. React 重新创建虚拟 DOM 树
  4. React 比较旧 DOM 与 新 DOM 的区别
  5. 如果有区别,则重新渲染该组件,也就是重新调用 render() 方法

React Ajax

说明

  1. React 本身只关注于界面, 并不包含发送 Ajax 请求的代码

  2. 前端应用需要通过 Ajax 请求与后台进行交互(json数据)

  3. React 应用中需要集成第三方 Ajax 库(或自己封装)

常用的 Ajax 请求库

  1. jQuery: 比较重,如果需要另外引入不建议使用

  2. axios: 轻量级,建议使用

  • 封装 XmlHttpRequest 对象的 Ajax

  • promise 风格

  • 可以用在浏览器端和 node 服务器端

  1. fetch: 原生函数,但老版本浏览器不支持
  • 不再使用 XmlHttpRequest 对象提交 Ajax 请求

  • 为了兼容低版本的浏览器,可以引入兼容库 fetch.js

在 Reate 中使用 Ajax

这里使用 axios 来获取远程数据

  1. 引入 axios <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.20.0-0/axios.js" type="text/javascript"></script>
  2. 在 React 中使用
componentDidMount() {
    const url = "localhost:8080/api/item";
    axios.get(url).then(response => {
        const { name, age } = response.data.items;
        this.setState({ name: name, age: age });
    }).catch(error => {
        //异常获取
        console.log(error.message);
    });
}

Fetch

Fetch 是 React 原生的异步请求工具,使用:

componentDidMount() {
    const url = "localhost:8080/api/item";
    //默认是 get 请求
    fetch(url).then(response => {
        return response.json();
    }).then(data => {
        const { full_name, html_url } = data.items[0];
        this.setState({ repoName: full_name, repoUrl: html_url });
    }).catch(error => {
        //异常获取
        console.log(error.message);
    });
}

Fetch 的 get 请求:

fetch(url).then(response => {
    return response.json();
}).then(data => {
    console.log(data)
}).catch(e => {
    console.log(e)
});

Fetch 的 post 请求:

fetch(url, {
  method: "POST",
  //要发送的数据
  body: JSON.stringify(data),
}).then(data => {
    console.log(data)
}).catch(e => {
    console.log(e)
});

组件间的通信

  1. 组件间常用的通信可以使用 props,例如上文那样传递数值或方法;但 props 传递只能一层一层传递,比如组件C要调用组件A的方法,而A中渲染B,B中再渲染C,方法就只能从 A->B->C 。
  2. 使用消息订阅(subscribe)、发布(publish)机制
  3. redux,后续说明

消息订阅发布机制

发布(publish)消息就像是去调用其他组件内的方法,将参数主动传递过去,而订阅(subscribe)像是定义了一个方法,等待别人来调用我。

使用

  1. 需要使用到 PubSubJS 库,在项目下npm install pubsub-js --save安装

  2. 在项目中引入import PubSub from 'pubsub-js'

  3. 发布消息:

//public('方法名',参数);
PubSub.public('search', searchName);
  1. 订阅消息:
//subscribe('方法名',回调函数);
PubSub.subscribe('search', (msg, searchName) => {
    console.log(msg);
    //操作...
});

React-router

介绍

React-router 就是 React 路由组件,是一个专门的插件库,带有自己的组件标签;专门用来实现一个 SPA 应用

SPA 应用的理解

  1. (single page web application)单页的 web 应用,React 中是组件套组件的开发方式,例如点击 nav 导航栏的链接,在浏览器中看似是在跳页面,其实只是改变了组件,页面并没有改变。

  2. 一个应用只有一个完整页面

  3. 点击页面按钮或链接不会刷新页面,当然也不会向服务器发请求

  4. 点击链接渲染另一个组件,做页面的局部刷新

  5. 数据都通过 Ajax 异步获取

对路由的理解

路由是什么?

一个路由就是一个键值对映射关系,key 就好比是对应组件的 url,value 就是即将渲染的组件名

路由分类(前台路由、后台路由)

前台路由例如:<Route path="/myComponent" component={MyComponent}>,当浏览器的 hash 变为 #myComponent 时, 当前路由组件就会变为 MyComponent 组件

后台路由例如:router.get(path, function(req, res)),当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据

前台路由实现

  • history库
  1. 网址: https://github.com/ReactTraining/history

  2. 管理浏览器会话历史(history)的工具库

  3. 包装的是原生 DOM 中 window.history 和 window.location.hash 对象

  • history API
  1. History.createBrowserHistory(): 得到封装 window.history 的管理对象

  2. History.createHashHistory(): 得到封装 window.location.hash 的管理对象

  3. history.push(): 添加一个新的历史记录

  4. history.replace(): 用一个新的历史记录替换当前的记录

  5. history.goBack(): 回退到上一个历史记录

  6. history.goForword(): 前进到下一个历史记录

  7. history.listen(function(location){}): 监视历史记录的变化

React-router 相关 API

组件

  1. <BrowserRouter>

  2. <HashRouter>

  3. <Route>

  4. <Redirect>

  5. <Link>

  6. <NavLink>

  7. <Switch>

路由基本使用

先安装 router 组件:在项目目录命令行下npm install --save react-router

引入<NavLink>组件:import {NavLink} from 'react-router-dom'

在项目入口处的 index.js 中要引用<BrowserRouter>

import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './components/App'

render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
    , document.getElementById("root")
);

<NavLink>组件是一个基本的导航链接组件,使用例如:

<div className="list-group">
    <NavLink className="list-group-item" to='/about' >About</NavLink>
    <NavLink className="list-group-item" to='/home'>Home</NavLink>
</div>

下面是前台路由组件:

<div className="panel-body">
    <Switch>
        <Route path='/about' component={About} />
        <Route path='/home' component={Home} />
        <Redirect to='/about' />
    </Switch>
</div>

<NavLink>的 to 属性与<Route>的 path 属性一致,component 属性对应要渲染的控件。
在这里插入图片描述
在这里插入图片描述

嵌套路由的使用

嵌套路由就是在一级路由下的组件中,再定义其他路由。在上文的案例中的 Home 组件中再定义路由:

export default function Home() {
    return (
        <div>
            <h2>Home组件内容</h2>
            <div>
                <ul className='nav nav-tabs'>
                    <li>
                        <NavLink to='/home/news'>News</NavLink>
                    </li>
                    <li>
                        <NavLink to='/home/message'>Messages</NavLink>
                    </li>
                </ul>
            </div>
            <div className="panel-body">
                <Switch>
                    <Route path='/home/news' component={News} />
                    <Route path='/home/message' component={Messages} />
                    <Redirect to='/home/news' />
                </Switch>
            </div>
        </div>
    )
}

<Redirect>组件相当于默认的路由,当 Home 组件渲染完成时,自动渲染 News 组件。
在这里插入图片描述
在这里插入图片描述

向路由组件中传递参数

延续上文案例,Messages 组件中包含三个链接,点击其中一个链接并传入不同 id,在 Messages 组件的下方显示对应 id 的信息,先看看 Messages 组件:

import React, { Component } from 'react'
import { Route, NavLink } from 'react-router-dom'
import Message_detai from './Message_detai'

export default class Mssages extends Component {

    state = { messages: [] }

    componentDidMount() {
        let messages = [
            { id: 1, title: 'message001' },
            { id: 3, title: 'message003' },
            { id: 5, title: 'message005' },
        ];
        this.setState({ messages });
    }

    render() {
        return (
            <div>
                <ul>
                    {
                        this.state.messages.map((element, index) => {
                            return <li key={index}><NavLink to={`/home/message/messagedetail/${element.id}`}>{element.title}</NavLink></li>
                        })
                    }
                </ul>
                <Route path='/home/message/messagedetail/:id' component={Message_detai} />
            </div>
        )
    }
}

<NavLink>将 messages 数组中的 id 的赋予链接,<Router>中使用 :id 表示所传的参数,格式也就是:参数名。再看 Message_detai:

import React from 'react'

const messageDetails = [
    { id: 1, title: 'Message001', content: 'content1' },
    { id: 2, title: 'Message002', content: 'content2' },
    { id: 3, title: 'Message003', content: 'content3' },
]

export default function Message_detai(props) {

    const { id } = props.match.params;
    const msg = messageDetails.find((msg) => msg.id === id * 1);

    return (
        <ul>
            <li>id:{msg.id}</li>
            <li>title:{msg.title}</li>
            <li>content:{msg.content}</li>
        </ul>
    )
}

const { id } = props.match.params;是从 props 中获取名为 id 的参数,messageDetails.find( function(){} )是遍历数组,直到方法返回 true 时返回此刻遍历到的对象,(msg) => msg.id === id * 1,msg 就是数组中的对象,当两个 id 相等时返回 true,因为从 props 中获取的 id 值类型是字符串,所有要*1变成数字。
在这里插入图片描述

Redux

Redux 是什么

  1. redux是一个独立专门用于做状态管理的JS库(不属于 react 插件库)

  2. 它可以用在 react, angular, vue 等项目中, 但基本与 react 配合使用

  3. 作用:集中式管理 react 应用中多个组件共享的状态(变量),可以说 redux 就是将多个组件共享的变量封装到一个 js 中,对变量增删改查的方法也封装到 js 中,解决组件间各种传参而导致关系复杂冗余的问题

Redux 工作流程

在这里插入图片描述
先了解 redux的三个核心概念:action、store、reducer

  1. reducer 中封装了对变量的操作方法,传入方法的参数为 state 与 action,state 不是react 中的状态,只是一个变量;action 是一个对象,表明要对 state 做什么操作;使用例如:
//对 state 进行增减的方法
// state = 0 表明state的初始值为0
export function count(state = 0, action) {
    switch (action.type) {
        case 'add':
            return state + action.data;
        case 'cut':
            return state - action.data;
        default:
            return state;
    }
}
  1. action 就是传入 reducer 方法中的对象,包含 type、data 属性,type 值为字符串,类似于方法名,具有唯一性,data 就是数据,值类型任意;type 为必要参数,data 为可选参数。使用例如:
//增加
export const INCREMENT = (number) => ({ type: 'add', data: number })
//减少
export const DECREMENT = (number) => ({ type: 'cut', data: number })
  1. store 是将 state,action,reducer 联系在一起的对象。react 组件调用store.getState()获取 state 值,调用store.dispatch(action对象)方法去修改 state,调用store.subscribe(渲染组件)去重新渲染组件。使用例如:
this.props.store.dispatch(actions.INCREMENT(count));

工作流程:

react 组件首先要拿到一个 action 对象,可以在一个 js 中定义很多 action 对象,再去获取 store 对象,store 对象可由父组件通过 props 传给子组件,调用 store 的 dispatch 方法传入 action,然后进入 reducer 的方法中,reducer 判断 action 的 type ,对 state 进行操作,返回一个新得 state 给 store,state 一旦改变,则会触发 store 的 subscribe 方法。

什么情况下需要使用 redux

  1. 总体原则: 能不用就不用,如果不用比较吃力才考虑使用

  2. 某个组件的状态,需要共享

  3. 某个状态需要在任何地方都可以拿到

  4. 一个组件需要改变全局状态

  5. 一个组件需要改变另一个组件的状态

对 state 进行增减的案例代码:

npm install --save redux安装 redux

项目结构:
在这里插入图片描述
先创建 store.js :

//store.js
import { counter } from './reducers'
import { createStore } from 'redux'

//创建一个 store 对象,传入 reducer 中的方法
const store = createStore(counter);

export default store

inedx.js :

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import store from './redux/store'

function render() {
    ReactDOM.render(<App store={store} />, document.getElementById('root'));
}

//初始化渲染
render();

//订阅监听,store中状态变化触发该函数
store.subscribe(render);

actios.js :

//存放 action 对象的 js
import { increment, decrement } from './action_types'
//增加
export const INCREMENT = (number) => ({ type: increment, data: number })
//减少
export const DECREMENT = (number) => ({ type: decrement, data: number })

action_types.js :

//action 对象的type类型
export const increment = 'increment'
export const decrement = 'decrement'

reducers.js :

/**
 * 包含n个reducer的模块
 */
import { increment, decrement } from './action_types'

export function counter(state = 0, action) {
    switch (action.type) {
        case increment:
            return state + action.data;
        case decrement:
            return state - action.data;
        default:
            return state;
    }
}

主组件 App.jsx :

import React from 'react'
import * as actions from '../redux/actions'

export default class App extends React.Component {

    add = () => {
        const count = this.select.value * 1;
        this.props.store.dispatch(actions.INCREMENT(count));
    }

    cut = () => {
        const count = this.select.value * 1;
        this.props.store.dispatch(actions.DECREMENT(count));
    }

    addIfOdd = () => {
        if (this.props.store.getState() % 2 === 1) {
            const count = this.select.value * 1;
            this.props.store.dispatch(actions.INCREMENT(count));
        }
    }

    render() {
        const count = this.props.store.getState();
        return (
            <div>
                <p>click {count} times</p>
                <div>
                    <select ref={select => this.select = select}>
                        <option>1</option>
                        <option>2</option>
                        <option>3</option>
                    </select>&nbsp;
                    <button onClick={this.add}>+</button>&nbsp;
                    <button onClick={this.cut}>-</button>&nbsp;
                    <button onClick={this.addIfOdd}>add if 奇数</button>&nbsp;
                </div>
            </div>
        )
    }
}

react-redux

之前的 react 组件中还存在着 redux 代码,可使用 react-redux 插件将所有组件分为制作 UI 的组件类与管理数据和业务的 redux 类。

  • UI组件
  1. 只负责 UI 的呈现,不带有任何业务逻辑

  2. 通过 props 接收数据(一般数据和函数)

  3. 不使用任何 Redux 的 API

  4. 一般保存在 components 文件夹下

  • 容器组件
  1. 负责管理数据和业务逻辑,不负责UI的呈现

  2. 使用 Redux 的 API

  3. 一般保存在 containers 文件夹下

将之前的对 state 操作的代码进行修改

首先安装npm install --save react-redux

将 App.jsx 中的 redux 代码整合到另一个 js 中,将 App.jsx 更名为 Counter.jsx:

/*
UI组件: 不包含任何redux API
 */
import React from 'react'
import PropTypes from 'prop-types'

export default class Counter extends React.Component {

    //使用 props 接受参数
    static propTypes = {
        count: PropTypes.number.isRequired,
        increment: PropTypes.func.isRequired,
        decrement: PropTypes.func.isRequired
    }

    add = () => {
        const count = this.select.value * 1;
        this.props.increment(count);
    }

    cut = () => {
        const count = this.select.value * 1;
        this.props.decrement(count);
    }

    addIfOdd = () => {
        if (this.props.count % 2 === 1) {
            const count = this.select.value * 1;
            this.props.increment(count);
        }
    }

    render() {
        const { count } = this.props;
        return (
            <div>
                <p>click {count} times</p>
                <div>
                    <select ref={select => this.select = select}>
                        <option>1</option>
                        <option>2</option>
                        <option>3</option>
                    </select>&nbsp;
                    <button onClick={this.add}>+</button>&nbsp;
                    <button onClick={this.cut}>-</button>&nbsp;
                    <button onClick={this.addIfOdd}>add if 奇数</button>&nbsp;
                </div>
            </div>
        )
    }
}

当然被抽取出来的 redux 代码给 Counter.jsx 传值,其被封装为 App.js:

import React from 'react'
import { connect } from 'react-redux'
import { INCREMENT, DECREMENT } from '../redux/actions'
import Counter from '../components/Counter'


//将redux与react联系起来,为props属性赋值
//该方法用于包装 UI 组件生成容器组件,将参数赋予 Counter
export default connect(
    state => ({ count: state }),
    { increment: INCREMENT, decrement: DECREMENT }
)(Counter)

index.js 中 :

import React from 'react';
import ReactDOM from 'react-dom';
import App from './container/App';
import store from './redux/store'
import { Provider } from 'react-redux'

ReactDOM.render(
    //Provider让所有组件都可以得到state数据,并维护store
    <Provider store={store}>
        <App />
    </Provider>
    , document.getElementById('root')
);

代码结构:
在这里插入图片描述
最早 react 组件是获取 action 对象,再获取 store 对象,拿 store 去调用方法,现在优化后组件与 redux 隔离开来,直接调用从外部传来的方法。connect 将 store 与组件连接在一起。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值