React 从入门到入门 02

本文详细介绍了React的基础知识,包括组件的生命周期,如何在生命周期方法中管理定时器,以及如何使用create-react-app创建项目。还探讨了React的Ajax请求,推荐使用axios库,并讲解了如何利用PubSubJS优化组件间通信。此外,文章深入讲解了react-router4的使用,包括注册路由、嵌套路由和路由切换,并提到了react-ui库和Redux的状态管理,包括Redux的基本概念、如何使用以及如何处理异步操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React 从入门到入门

组件的生命周期

  • 给元素添加样式的时候需要两个大括号,例如:
// 外层大括号表示里面是 js 代码,里面的大括号表示内容是个对象
<h2 style={{ opacity: opacity }}>{this.props.msg}</h2>
  • 经常在 componentDidMount() 生命周期中启动定时器 (挂载完毕的勾子函数)
  • componentWillUnmount() 中获取停止定时器等操作 (移除组件的时候调用的方法)

注意:setInterval 和 setTimeout 里的 this 指向的都是 window

  • 在节点上卸载组件方法:
// 卸载 .example 里面的组件
ReactDOM.unmountComponentAtNode(document.querySelector('.example'))
  • 实例:让一句话每隔 2 秒重新设置一下透明度,并且有一个按钮可以直接清空这个组件
class Life extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
        this.state = {
            opacity: 1,
            intervalId: 0
        }
    }
    handleClick() {
        // 删除整个组件
        ReactDOM.unmountComponentAtNode(document.querySelector('.life'))
    }
    // 组件渲染到 DOM 中时的生命周期函数
    componentDidMount() {
        const intervalId = setInterval(function () {
            let { opacity } = this.state;
            opacity -= 0.1;
            if (opacity <= 0) {
                opacity = 1;
            }
            // 这里我把 intervalId 放在了 state 里面,老师的操作是直接将 intervalId 放在了组件对象里 (this.intervalId)
            this.setState({ opacity, intervalId });
        }.bind(this), 200);
    }
    // 组件将要删除时候触发的生命周期函数
    componentWillUnmount() {
        // 清理定时器
        clearInterval(this.state.intervalId)
    }
    render() {
        let { opacity } = this.state
        return (<div>
            // 外层大括号表示里面是 js 代码,里面的大括号表示内容是个对象
            <h2 style={{ opacity: opacity }}>{this.props.msg}</h2>
            <button onClick={this.handleClick}></button>
        </div>)
    }
}
ReactDOM.render(<Life msg="React tql!" />, document.querySelector(".life"));
  • 重要的勾子:
    1. render():初始化渲染或更新渲染调用
    2. componentDidMount():开启监听,发送 ajax 请求
    3. componentWillUnmount():做一些收尾工作,如:清理定时器
    4. componentWillReceiveProps():后面需要时讲

React 脚手架

  • 在要安装的文件夹下执行 npm init react-app my-app 他会在该目录下安装 React app

  • package.json 里面的 dependencies 是运行时依赖,devDependencies 是开发时依赖 (编译打包)

  • 在 react 中,class 都要改变为 className


  • 使用 create-react-app 创建应用 (两种方法安装的结果是一样的)
  1. 全局安装 react 脚手架 (create-react-app):npm install -g create-react-app
  2. 创建项目:create-react-app 项目名
  3. 进入这个项目:cd 项目名
  4. 运行项目:npm start
  • 打开 localhost:3000 就能看到效果
  • 写组件的时候最好把文件名写成 jsx 文件类型
  • 组件的基本写法:
import React, { Component } from 'react'
class App extends Component {
    render() {
        return (<div></div>)
    }
}

// 暴露给外界
export default App;
  • 或者暴露给外界也可以和上面的写到一起
export default class App extends Component {
    render() {
        return (<div></div>)
    }
}
// 效果和上面是一样的
  • 运行时需要在 index.js 上引入要使用的包 (包括css,react库,要使用的组件等)

  • 在组件类里面加上 static 关键字是给组件类添加属性

  • 例如我想让组件检查传入数据的类型

static propTypes = {
    // 要求为数组类型且必须有这个属性
    comments: propTypes.array.isRequired
}
  • 使用 propTypes 时需要先自己下载 npm install --save prop-types
  • 绑定事件也可以这样写避免修改 this
class App extends Component {
    // 用箭头函数来避免修改 this
    handleClick = () => {
        console.log('React');
    }
}

React Ajax

  • React 并不包含发送 Ajax 请求的代码

  • 经常使用第三方库来发送请求 (axios)

  • axios 是 promise 风格的

  • fetch 是原生函数,也可以发送 Ajax 请求,但是老版浏览器不支持,老版浏览器使用时需要引入兼容库 fetch.js

  • 使用 axios 发送 Ajax 请求

// 在生命周期函数里发送 ajax 请求
componentDidMount() {
    // 这个接口地址已经作废不能使用
    const url = `https://api.github.com/search/repositories?q=${repoName}e&sort=stars`
    axios.get(url).then(response=> {
        const result = response.data;
        // 得到数据
        const {name,html_url} = result.item[0];
        // 更新状态
        this.setState({repoName:name,repoUrl:html_url});
        // 错误处理
    }).catch((err)=>{
        console.log(err.message);
    })
}
  • post 请求:axios.post(url,{参数})

  • 使用 fetch.js 发送 Ajax 请求:

fetch(url).then(response=>{
    return response.json()
}).then(data => {
     // 得到数据
    const {name,html_url} = data.item[0];
})
  • 生命周期:componentWillReceiveProps(newprops) 在组件收到新的属性时调用 (可以用来监听属性值的改变)

组件间信息传递优化

  • 解决爷孙组件之间传值和兄弟组件之间的传值的不方便,使用 PubSubJS 工具库来解决这个问题

  • 消息的订阅 (subscribe) 与发布 (publish)

  • 需要安装 PubSubJS 工具库

  • 下载:npm install --save pubsub-js

  • 使用时需要引入:import PubSub from 'pubsub-js'

  • 发布消息:

// 第一个参数:第二个参数:要传送的数据
PubSub.publish('search', index)
  • 订阅消息:
  • 订阅消息 (接收参数) 经常写在:componentDidMount() 生命周期函数中
PubSub.subscribe('search', (msg, data) => {
    // 这里 msg 是消息名 (也就是 search),第二个参数才是订阅返回的参数 (最好改成传送数据的名字)
    console.log(msg, data);
})
  • 注意,里面的回调函数的 this 是指向 PubSub 的,所以最好把回调函数改为箭头函数,让 this 重新指向组件对象

react-router4 的使用

  • 他是一个 react 插件库,并且使用非常广泛
  • SPA 应用 (single page web application):
    • 整个应用只有一个完整的页面
    • 点击页面中路由链接不会刷新页面,本身也不会像服务器发送请求 (点击路由链接时会发生页面的局部更新)
    • 数据通过 Ajax 请求来获取,在前端异步展现

注册路由

  • 分为前端路由和后端路由,一个路由对应一个映射关系

  • 前端路由请求的 path 是 key,回调函数或组件 (component) 是 value

  • 前端路由注册路由:<Route path="/about" component={about}>

  • 创建 history 对象 (两种方法任选一种)

let history = History.createBrowserHistory();
history = History.createHashHistory();
// 第二种方法利用的是锚点链接,利用锚点链接不用发送请求还可以被历史记录记住的特点
  • 历史记录像一个栈的结构,有四种操作方法
// 跳转到这个页面,并且把页面压入栈中
history.push(url);
// 回退
history.goBack();
// 前进
history.goForword();
// 跳转到这个页面,并且替换掉栈顶的页面
history.replace(url);
  • 路由链接与非路由链接:路由链接是不向服务器发送请求的 (例如 <NavLink /> 标签),而非路由链接是会向服务器发送请求 (例如 a 标签)

react-router 的相关使用

  • 使用:npm install --save react-router-dom (安装 web 的版本需要加上 -dom)

  • 现在的组件分为路由组件和非路由组件,两种组件存放在两个不同的文件夹中 (非路由组件 pages/views)

  • 只能显示其中一个的时候需要用 Switch 标签进行包裹

  • 引入组件:import { BrowserRouter } from 'react-router-dom' 大括号里放上你想要使用的组件

<NavLink to="/about">about</NavLink>
<NavLink to="/home">home</NavLink>
// 只能显示下面两个 Router 中的一个
<Switch>
    // path 绑定哪个 NavLink 标签,component 绑定哪个组件
    <Route path="/about" component={About}></Route>
    <Route path="/home" component={Home}></Route>
    // 跳转标签,决定跳转到哪个页面
    <Redirect to="/about"></Redirect>
</Switch>
  • 使用 react-router 后不能直接渲染 app,需要用标签包裹着
<BrowserRouter>
    <App />
</BrowserRouter>
  • NavLink 与 Link 的区别是,NavLink 可以选择绑定动态样式 (也就是选中时候添加的类名):activeClassName

  • 假如我每一个组件都想写 activeClassName 属性,但是又很麻烦,这时我就可以用一个组件包裹这个标签

export default class MyNavLink extends Component {
    render() {
        // 将外部传入的所有属性传给 NavLink
        return <NavLink {...this.props} activeClassName="temp"></NavLink>
    }
}

嵌套路由

  • 嵌套路由和普通路由差不多,同样要在父组件里写好路由
  • 需要注意的是路径要从根路径开始写,否则就会找不到
// 父组件中的路由
<NavLink to="/home/news">news</NavLink>
<NavLink to="/home/message">message</NavLink>
<Switch>
    <Route path="/home/news" component={News}></Route>
    <Route path="/home/message" component={Message}></Route>
    <Redirect to="/home/news"></Redirect>
</Switch>
通过路由链接传递数据
  • a 标签后面跟上一个 id 值,使用 Route 标签传值进行接收
  • 例如:
// 通过后面添加 id 属性来传递数据
<NavLink to={`/home/message/messagedetail/${m.id}`}></NavLink>
<Route path="/home/message/messagedetail/:id" component={MessageDetail} />
// 通过 params 获取参数中的 id
const { id } = props.match.params;

路由切换的两种方式

  • 我们不止希望能通过链接的方式实现路由的跳转,我们也希望能通过点击按钮等事件来实现路由跳转
  • 因为路由切换相当于调用了 history.push() 函数,所以只要监听按钮点击事件然后执行 push 函数就可以了
  • 除了使用 push 函数进行切换,还可以使用 replace 函数进行切换 (区别在于回退的时候 push 返回到点击之前,replace 直接返回到点击之前的上一个页面)
// 在组件内的事件
showDetail = (id) => {
    this.props.history.push(`/home/message/messagedetail/${id}`)
}
  • 在 html 中的 button
<button onClick={() => this.showDetail(m.id)}>查看</button>
  • 里面 onClick 添加一个箭头函数是因为 要是直接在里面写函数的话 (onClick={this.showDetail()}) 这样写无法传参,但是加上一个箭头函数以后就可以在里面写入想要的参数了

使用 js 实现页面跳转:window.location = '网址'

react-ui 库的使用

  • 目前比较流行的 ui 库:material-ui (国外),ant-design (蚂蚁金服)

  • ant-design 针对两套,一套面向移动端 (ant-design-mobile),一套面向 pc 端 (ant-design)

  • 使用 npm 安装这个库:npm install antd-mobile --save

移动端的点击事件是有一小会的延时的,需要添加 fastclick.js 库进行处理,让点击没有延时

  • 使用这个 ui 库的方法:
<!-- 官网给出的模板代码,在 index.html 上添加这段代码 -->
<!-- set `maximum-scale` for some compatibility issues -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
    document.addEventListener('DOMContentLoaded', function() {
    FastClick.attach(document.body);
    }, false);
}
if(!window.Promise) {
    document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
}
</script>
  • 在组件上引入想要使用的 ui 组件:
import { Button } from 'antd-mobile'
  • 在 index.js 上引入样式:
// 不推荐
import 'antd-mobile/dist/antd-mobile.css'
  • 但是这种方法是将整个文件全部引入,不复合按需打包的特性 (下面会解决这个问题)

  • 可以去官网去查询如何调整按钮的样式 (惊奇的是 vscode 对这个 ui库居然有语法提示)

  • 并且这个 ui 库也支持 Toast 轻提示组件 (和小程序好像啊)

handleClick = () => {
    Toast.info('提交成功');
}

实现按需打包

  • 老师教的老版本的教程可以使用,但是新版的还是需要到官网进行配置
  • 需要安装两个库 npm install react-app-rewired customize-cra --save-dev
  • 修改 package.json文件中的 script 属性
"scripts": {
   "start": "react-app-rewired start",
   "build": "react-app-rewired build",
   "test": "react-app-rewired test --env=jsdom",
}
  • 在项目根目录下创建一个 config-overrides.js,并在里面输入内容
const { override, fixBabelImports } = require('customize-cra');
  module.exports = override(
    fixBabelImports('import', {
      libraryName: 'antd-mobile',
      style: 'css',
    }),
  );
  • 在之后正常引入他就会自动添加所需要的 css 文件
import { Button } from 'antd-mobile'

redux 的介绍

  • redux 是一个专门用来状态管理的 js 库 (不是 react 插件库,但经常和 react 一起使用)

  • 集中管理 react 应用中多个组件共享的状态

  • 下载依赖:npm install --save redux

  • 创建 redux 组件的时候,需要创建这四个文件

    • action-types.js:定义 action type 常量字符串
    • action.js:创建 action 工厂函数的文件
    • reducers.js:根据老的 state 和 action 更新出新的 state (switch case 语句)
    • store.js:生成一个 store 对象,并且把他暴露出去

redux 的基本使用

  • store 对象是一个存放数据的容器,一个应用只能有一个 store
  • 生成一个 store 对象
// 在 index.js 中
import {createStore} from 'redux'
// 里面需要传递 reducer 函数作为参数,返回新生成的 store 对象
const store = createStore(reducer); // 第一次调用 reduer 函数得到初始 state
// 把 store 对象传给组件 app.jsx
ReactDOM.render(<App store={store} />,);
// 监听每次修改 store 后重绘组件
store.subscribe(function () {
    ReactDOM.render(<App store={store} />,);
});
reducers 函数
  • 根据老的 state 和 action 产生新的 state 的纯函数
  • 返回一个新的状态,不修改原来的状态

纯函数:相同的输入必定得到相同的输出,在函数执行的过程中没有副作用 (例如没有更改文件系统,操作数据库等)

  • 感觉是他对 state 的更新,分为几种更新情况:
// reducers.js 包含多个 reducer 的模块
// 注意这里不要写 default 否则只会有一个函数被暴露在外面
// 在写函数的时候给 state 写默认值他会通过 default 返回给 store 初始值
export function counter(state = 0, action) {
    switch(action.type) {
        // 可以定义一个模块 (action-types.js) 来定义常量来储存这些字符串 (这样就会有语法提示防止写错)
        case 'INCREMENT':
            return state + action.data;
        case 'DECREMENT':
            return state - action.data;
        default:
            return state;
    }
}
在组件中更新状态
// 获取 store 中的数据 (注意不是解构)
const count = this.store.getState()
// 更新 state 中的数据
this.props.store.dispatch({type:'INCREMENT',data:number});
工厂函数创建 action
  • action:标识要执行行为的对象
  • 包括两个方面的属性:
    • type:标识属性,值为字符串,唯一且必要属性
    • xxx:数据属性,值类型任意,可选属性
const action = {
    type: 'INCREMENT',
    data: 1
}
  • Action Creator (创建 Action 的工厂函数):
// 可以单写一个文件 (actions.js) 来定义工厂函数
const increment = (number) => ({type:'INCREMENT',data:number});

什么是工厂函数:
1,它是一个函数。
2,它用来创建对象。
3,它像工厂一样,“生产”出来的函数都是“标准件”(拥有同样的属性)

  • 这样再更新 state 状态:
this.props.store.dispatch(actions.increment(number));

react-redux 的使用

  • 上面版本里 redux 和 react 耦合度太高
  • 又要安装一个库 react-redux 去简化在 react 应用中使用 redux
  • 安装:npm install --save react-redux
  • 使用这个插件以后不用使用 subscribe 监听 state 更新,而是用它里面的 Provider 组件把要渲染的组件包起来
// index.js
import {Provider} from 'react-redux';
ReactDOM.render((<Provider store={store}>
    <App />
</Provider>),document.getElementById('root'));
  • 组件中:
// app.jsx
import {connect} from 'react-redux';
// 在组件中接收这三个属性
static propTypes = {
    // count 要获取的 state 中的数据
    count: PropTypes.number.isRequired,
    // 更新 state 所需要的两个 action 函数
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired
}
// app 组件不能暴露,使用 connect (以后下面这部分需要包裹成 container 组件,和 UI 层分开)
export default connect(
    state => ({count: state}),
    {increment, decrement}
)(App)
  • react-dedux 思想:把所有组件分为两种组件:一种是 UI 组件 (没有用到 react-dedux的组件),一种是容器组件 (使用 react-dedux 包裹 UI 组件的组件)

感觉 React 越到后面组件化越严重,甚至上面的 connect 需要包裹 App 这部分都要写成两个组件

redux 异步问题 (redux-thunk)

  • redux 默认不能异步处理,但是应用中又存在异步的任务 (ajax,定时器)

  • 又来新插件了,redux-thunk (异步中间件解决异步编程)

  • 安装:npm install --save redux-thunk

  • redux 除了有 createStore 方法之外还有 applyMiddleware 方法 (应用中间件)

// store.js
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
// 在 createStore 上还有第二个参数,应用上异步中间件 thunk
const store = createStore(counter, applyMiddleware(thunk))
  • 在 action.js 中定义异步方法:
export const increment = (number) => ({type: 'INCREMENT',data: number})
// 异步 action 返回一个函数
export const incrementAsync = (number)  => {
    return dispatch => {
        // 异步的代码
        setTimeout(() => {
            // 1s 之后去分发一个 action
            dispatch(increment(number));
        },1000)
    }
}

redux 调试工具

  • 当我们使用 redux 来保管 state 的时候在调试工具中就看不到了
  • 要安装 redux-devtools 插件,还需要在工程里安装依赖包:npm install --save-dev redux-devtools-extension
  • 然后在 store.js 文件中加入如下语句:
import {composeWithDevTools} from 'dedux-devtools-extension';
// 外层仍然需要一个函数进行包裹 (和 redux-thunk 一样)
const store = createStore(
    counter,
    composeWithDevTools(applyMiddleware(thunk))
);

小知识:在编辑器中切换大小写:Ctrl + Shift + x (仅在 webStorm 中有用,在 vscode中是打开扩展)

整合 reducer

  • redux 里面有一个函数 combineReducers 将多个 reducer 合并一起管理
  • 将其他函数去除暴露,然后让 combineReducers 统一处理
// reducers.js
export default combineReducers({
    // 里面写想暴露的函数名
    counter,
    comments
});
  • 暴露方法:
import reducers from '路径'
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值