react学习笔记

it2023-08-05  81

react

构建用户界面的js库

和vue的区别: 比vue更加灵活简便小巧,因为小巧所以更加复杂

主要用于构建ui界面

2013年开源

特点:

声明式的设计高效, 采用虚拟dom实现渲染,最大限度减少dom操作灵活, 跟其他的库灵活搭配使用jsx, 俗称js中写html, js的语法拓展组件化, 模块化开发, 代码复用性提高, 大型项目非常喜欢用react单项数据流, 没有实现数据双向绑定 数据- > 视图 -> 事件 ->数据

创建项目

通过script引入使用 <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> 通过react脚手架创建项目进行开发, 部署 # 安装 creat react app npm install -g create-react-app npx create-react-app my-app # 或者 npm init react-app my-app cd my-app npm start

react元素渲染

注意

组件渲染不管是函数式组件还是类组件都需要引入

import React from 'react';

jsx

jsx是对于JavaScript语法的拓展, 可以在js中写HTML代码

需要注意的事情 jsx中 class属性 变更为 className

jsx语法 {}

jsx中可以写的语法

{name}{formatName(user)}<img src={user.avatarUrl}>{1+1}

jsx可以写为

const element = ( <h1 className="greeting"> Hello, world! </h1> );

注意: 组件名称必须以大写字母开头。

React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome。

你可以在深入 JSX中了解更多关于此规范的原因。

元素渲染有两种方式

函数式组件 需要返回一个jsx function App(props) { console.log(props) let name = "lidppp" function doSomeThing() { return name + "在学习react" } return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo"/> <p>我是{name}</p> <p>{doSomeThing()}</p> <p> Edit <code>src/App.jsx</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App; 类组件 export default class App extends React.Component { constructor(props) { super(props); this.state = { name: "李大炮", num: 0, } this.state.m_name = this.state.name } doSomeThing() { return this.state.name + "在学习react" } componentDidMount() { // 组件挂载后 this.setState({ m_name:this.state.name+"大佬" }) let n = 0 let ajax = new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(this.state.name+"大佬") },3000) }) ajax.then(res=>{ this.setState({ m_name:this.state.name+"大佬"+n }) }) } componentWillUnmount() { // 组件销毁后 clearInterval(this.InterNmb) } render() { return <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo"/> <p>我是{this.state.m_name}</p> <p>{this.doSomeThing()}</p> <OneCom name={this.state.m_name}></OneCom> <p> {this.props.name} Edit <code>src/App.jsx</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> } }

props

在函数式组件中props接收的位置在 方法的形参第一个

也就是function App(props)

在类组件中props接收的位置在constructor参数的第一个

也就是constructor(props)并且需要调用super(props)挂载的react.component基类上面

父组件传入props直接在jsx中写属性即可 <OneCom name={this.state.m_name}></OneCom>

和vue的区别是 在jsx中属性名需要直接写成小驼峰命名而不是vue中 xxx-xxx可以被转义为xxxXxx

注意

和vue中一样子组件无法修改父组件的props

并且数据流是向下传递的(父组件改变,子组件跟着改变,目前来看子组件无法给父组件提供数据)

state

目前暂时默认为函数式组件无法定义state, 下面的都已类组件来说

在组件中需要定义state属性,类似于vue的data

state保存的是一个对象

因为react没有双向数据绑定所以无法直接进行this.state.xxx = xxx这样的操作(其实也可以但是视图无法发生变化)

更新数据类似小程序的操作

需要调用setState({})

this.setState((state, props) => ({ // 可以接受state和props counter: state.counter + props.increment }));

个人理解,没有看源码, 仅仅为对现象的猜测

如果在state中定义了很多参数

如state={name:"张三",age:18,sex:0}

在调用setState方法的时候写入以下代码

setState({age:this.state.age+1})

发生改变的只有age这一个属性,其他属性均保留原来的值

个人猜测实现方式和Object.assign类似

生命周期

componentWillMount -------------------- vue中的 beforeMount 已过时

componentDidMount -------------------- vue中的mounted

shouldComponentUpdate -------------------- 返回true或者false 是否可以更改

componentWillReceiveProps -------------------- vue中没有对应的,当props改变的时候触发

根据官网的描述

在该函数(componentWillReceiveProps)中调用 this.setState() 将不会引起第二次渲染。

componentWillUpdate -------------------- vue中的beforeUpdate 已过时

componentDidUpdate -------------------- vue中的updated

componentWillUnmount -------------------- react中只有一个卸载钩子函数 字面意思理解的话应该是beforeDestroy

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JPXne9cw-1603191601624)(笔记.assets/image-20200809145607338.png)]

事件绑定

React 事件的命名采用小驼峰式(camelCase),而不是纯小写。使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。 render() { return (<div onClick={this.showmsg}> <p>{this.props.nameHello}</p> </div>) } 在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); } 当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数:

注意看下面 handleClick是一个箭头函数

class LoggingButton extends React.Component { // 此语法确保 `handleClick` 内的 `this` 已被绑定。 // 注意: 这是 *实验性* 语法。 handleClick = () => { console.log('this is:', this); } render() { return ( <button onClick={this.handleClick}> Click me </button> ); } }

Create React App 默认启用此语法。

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:

class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // 此语法确保 `handleClick` 内的 `this` 已被绑定。 return (<button onClick={() => this.handleClick()}> Click me </button> ); } }

此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

传递参数

在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

条件渲染

元素变量条件渲染

render() { const isLoggedIn = this.state.isLoggedIn; let button; // 还记得jsx也是一个表达式吗,没错这里就用到了 if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); }

运算符 &&

function Mailbox(props) { const unreadMessages = props.unreadMessages; return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div> ); } const messages = ['React', 'Re: React', 'Re:Re: React']; ReactDOM.render( <Mailbox unreadMessages={messages} />, document.getElementById('root') );

三元运算符

render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. </div> ); } // 或者 render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn ? <LogoutButton onClick={this.handleLogoutClick} /> : <LoginButton onClick={this.handleLoginClick} /> } </div> ); }

阻止条件渲染

function WarningBanner(props) { if (!props.warn) { return null; } return ( <div className="warning"> Warning! </div> ); }

在组件的 render 方法中返回 null 并不会影响组件的生命周期。例如,上面这个示例中,componentDidUpdate 依然会被调用。

循环渲染

官方推荐使用map函数进行循环

map不改变原对象返回一个新对象

const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map((number) => number * 2); console.log(doubled);

渲染组件

const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((numbers) => <li>{numbers}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );

组件渲染

function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li key={number.toString()} {/*注意加key, key应该放在循环的最外层*/}> {number} </li> ); return ( <ul>{listItems}</ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') );

表单

受控组件

在 HTML 中,表单元素(如<input>、 <textarea> 和 <select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

import React from "react"; export default class Form extends React.Component{ constructor(props) { super(props); this.state = { value1:"", value2:"", value3:0, value4:"", } } val1Change = (e) => { this.setState({value1:e.target.value}) } submit(e){ e.persist(); e.preventDefault(); console.log("submit",e) } render() { return (<div> <form onSubmit={this.submit}> <div> <label htmlFor="username"> 用户名 </label> <input id="username" name="username" type="text" onChange={this.val1Change}/> <label htmlFor="username"> num </label> <input id="num" name="num" type="text" onChange={(e)=>{this.setState({value1:e.target.value})}}/> </div> <div> <label htmlFor="desc">文章</label> <textarea name="desc" id="desc" value={this.state.value4} onChange={(e)=>{e.persist();console.log(e);this.setState({value4:e.target.value})}} cols="30" rows="10"></textarea> </div> <div> <label htmlFor="sex">性别</label> <select value={this.state.value3} onChange={(e)=>{this.setState({value3:e.target.value})}} name="sex" id="sex"> <option value="-1">请选择性别</option> <option value="0">男</option> <option value="1">女</option> <option value="2">奇奇怪怪的性别</option> </select> </div> <div> <input type="submit" value={"提交"}/> </div> </form> <div> <p>name:{this.state.value1}</p> <p>desc:{this.state.value2}</p> <p>sex:{this.state.value3}</p> </div> </div>) } }

注意

你可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:

<select multiple={true} value={['B', 'C']}>

textarea标签和select标签在react中都是由value属性控制的

注意:

如果value设置方式是这样的 <input value="hi"> 是无法进行修改的, react会阻止input框修改

状态提升

所谓状态提升就是通过父级组件向下分发props

然后通过父级组件传入的方法修改props中的值

也就是子组件中修改父组件的值

组合vs继承

React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。

组合

我们建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中:

function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); }

这使得别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递给它们。

function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> Welcome </h1> <p className="Dialog-message"> Thank you for visiting our spacecraft! </p> </FancyBorder> ); }

在下方 <FancyBorder>中包裹的<h1>和<p>标签被当做props的children传入

类似于vue中的匿名插槽

当然, 也可以使用具名插槽, 如下所示

function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }

在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递。

特例关系

有些时候,我们会把一些组件看作是其他组件的特殊实例,比如 WelcomeDialog 可以说是 Dialog 的特殊实例。

在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:

function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> </FancyBorder> ); } function WelcomeDialog() { return ( <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" /> ); }

组合也同样适用于以 class 形式定义的组件。

function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> {props.children} </FancyBorder> ); } class SignUpDialog extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.state = {login: ''}; } render() { return ( <Dialog title="Mars Exploration Program" message="How should we refer to you?"> <input value={this.state.login} onChange={this.handleChange} /> <button onClick={this.handleSignUp}> Sign Me Up! </button> </Dialog> ); } handleChange(e) { this.setState({login: e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this.state.login}!`); } }

那么继承呢?

在 Facebook,我们在成百上千个组件中使用 React。我们并没有发现需要使用继承来构建组件层次的情况。

Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。

注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。

Fragment(对应vue中的template)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ynYaMHab-1603191601627)(笔记.assets/image-20200807212625285.png)]

看得出来像VUE中的template一样也是需要一个根div或者其他标签包裹的

如果不想要根div渲染, 解决方案为

import React, {Factory, Fragment} from "react"; function Asd(){ return ( <Fragment> <li>1</li> <li>2</li> </Fragment> ) } function Qwe(){ return ( <> <li>1</li> <li>2</li> </> ) }

渲染结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JtopzowO-1603191601628)(笔记.assets/image-20200807213518239.png)]

Ref

function CustomTextInput(props) { return ( <div> <input ref={props.inputRef} /> </div> ); } class Parent extends React.Component { constructor(props) { super(props); this.inputElement = React.createRef(); } render() { return ( <CustomTextInput inputRef={this.inputElement} /> ); } } // 现在你就可以在需要时设置焦点了 this.inputElement.current.focus();

react UI库 Antd

Ant Design of React

自己看文档去

react发送ajax请求

一般有三种方式

jQuery发送ajaxaxiosfetch

我琢磨应该是fetch用的比较多, 但是axios比较成熟 再说吧

react路由

印象中文文档

写的也不能说乱,就是看起来有点惆怅

基本使用

npm install react-router-dom

import React from "react"; import { BrowserRouter as Router, Route, Link } from "react-router-dom"; const BasicExample = () => ( {/*注意外层需要有Router包裹*/} <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/topics">Topics</Link> </li> </ul> <hr /> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route path="/topics" component={Topics} /> </div> </Router> );

exact 精准匹配

<Router> <div className="App"> hello <br/> <Link to={"/"}>INDEX</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/home"}>HOME</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/about"}>ABOUT</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/about/center"}>ABOUT/CENTER</Link> </div> <Route path="/home" component={Home}/> {/* exact为路由精准匹配 如果不加 /about/center 会同时显示 about和center 如果加了则只显示center */} <Route exact path="/about" component={About}/> <Route path="/about/center" component={Center}/> </Router>

如果不加exact 当匹配"/about/center"路径的时候会匹配到

"/about"和"/about/center" 两个路由, 会渲染两个组件

如果添加了exact属性则只能匹配到"/about/center"路由因为/about成为了精准匹配

路径浏览器路径exact是否匹配/one/one/twotrueno/one/one/twofalseyes

strict 反斜杠匹配

路径浏览器路径exact是否匹配/one/one/twotrueno/one/one/twofalseyes

警告:可以使用 strict 来强制执行 location.pathname 没有结尾斜杠,但为了执行此操作,strict 和 exact 必须都是 true 。

路径浏览器路径是否匹配/one/oneyes/one/one/no/one/one/twono

Switch

switch只匹配一个路由, 兼容上面两个 exact和strict精准匹配规则

404页面

注意:

当没有strict和exact属性时 如:<Route path="/about" component={About}/>

switch 会只匹配到 /about 下面的 /about/center不会被匹配到

所以侧面证明了 switch只能匹配一个路由

<Switch> <Route path="/home" component={Home}/> {/* exact为路由精准匹配 如果不加 /about/center 会同时显示 about和center 如果加了则只显示center */} {/*<Route strict exact path="/about" component={About}/>*/} {/* 当路由上添加了strict和exact时 访问/about/center Switch也一样不会匹配到 /about */} <Route strict exact path="/about" component={About}/> <Route path="/about/center" component={Center}/> {/*<Route path="*" component={NoPage}></Route>*/} {/*或者直接不写path*/} <Route component={NoPage}></Route> </Switch>

render 不引入组件直接渲染

<Route path="/demo" render={()=>{return (<div> 这是demo </div>)}}></Route>

Link高亮

使用NavLink

import {BrowserRouter as Router, Route, NavLink as Link,Switch} from "react-router-dom" <div className="App"> hello <br/> <Link to={"/"}>INDEX</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/home"}>HOME</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/about"}>ABOUT</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/about/center"}>ABOUT/CENTER</Link> <span style={{marginLeft: "8px"}}/> <Link to={"/demo"}>DEMO</Link> </div>

注意NavLink也会出现像Route组件一样的问题(瞎匹配)解决方案是像Route一样加上exact和strict属性就行了[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lyt8tYEr-1603191601630)(笔记.assets/image-20200810172745021.png)]

activeClassName

修改选中时附加的类名 默认为active

<NavLink activeClassName="selected">

activeStyle

选中时附加css属性

<NavLink activeStyle={{color:"red"}}>

路由跳转携带参数

<Route exact path="/query/:id" component={Query}/> // 使用 <div> 康康参数:{this.props.match.params.id} </div> // 可选参数 <Route exact path="/query/:id?" component={Query}/> // 传入多个参数 <Route exact path="/query/:id/:name?" component={Query}/>

query形式传参

?xxx=xxx&aaa=bbb形式传参

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WgGWoj1i-1603191601631)(笔记.assets/image-20200810175354080.png)]

var p = new URLSearchParams(props.location.search) p.get("xxx");//就可以拿到对应的值了

或者使用node.js的querystring.parse

Link to 传入 object

<Link to={{ pathname: '/courses', search: '?sort=name', hash: '#the-hash', state: { fromDashboard: true } }}/>

一个可以具有以下任何属性的对象:

pathname: 表示要链接到的路径的字符串。search: 表示查询参数的字符串形式。hash: 放入网址的 hash,例如 #a-hash。state: 状态持续到 location。

state存放在 props.location.state中 隐式传参

路由跳转携带参数

访问 / 路由的时候跳转到 /home

<Route exact path="/" render={()=><Redirect to="/home" />}/>

代码跳转路由

push(path, [state]) - (function 类型) 在 history 堆栈添加一个新条目replace(path, [state]) - (function 类型) 替换在 history 堆栈中的当前条目go(n) - (function 类型) 将 history 堆栈中的指针调整 ngoBack() - (function 类型) 等同于 go(-1)goForward() - (function 类型) 等同于 go(1)block(prompt) - (function 类型) 阻止跳转。(详见 history 文档)。 // push会写入到历史记录并且跳转页面 叠加 上一次的页面依然存在 this.props.history.push("/about"); // replace 会把跳转页面的url覆盖掉当前页面的历史记录中 替换 上一次的页面不存在 this.props.history.replace("/about");

withRouter

当组件没有被路由直接掌控的时候, 也就是路由页面中的子组件, props没有办法拿到路由对象的属性(history)

这个时候神兵降世, 可以通过withRouter组件访问history属性和最近的Route中的match 方案1

import React from "react"; import PropTypes from 'prop-types' import { withRouter } from 'react-router-dom' class HomeChild extends React.Component{ constructor(props) { super(props); } static propTypes = { match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired } btnClick = ()=>{ console.log(this.props) } render() { return <button onClick={this.btnClick}>未受掌控组件btn</button> } } export default withRouter(HomeChild)

方案2

// 在父级组件中传下来不就ok了吗 <HomeChild {...this.props}></HomeChild>

Prompt

用于在用户离开页面之前及时提示用户。当你的应用程序进入应阻止用户离开的状态时(比如一个表格被填满了一半),渲染一个 。

import React from "react"; import PropTypes from 'prop-types' import { withRouter,Prompt } from 'react-router-dom' class HomeChild extends React.Component{ constructor(props) { super(props); } static propTypes = { match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired } state = { val:"" } btnClick = ()=>{ console.log(this.props) } inputChang = (e)=>{ this.setState({ val:e.target.value.trim() }) } render() { return ( <div> <button onClick={this.btnClick}>未受掌控组件btn</button> <Prompt when={!!this.state.val} message={"干了,兄弟萌"}/> <input onChange={this.inputChang} /> </div> ) } } export default HomeChild

message: string

当用户试图离开时提示用户的消息。

<Prompt message="Are you sure you want to leave?"/>

message: func

将用户试图前往到的下一个 Location 和 action 调用。返回一个字符串以向用户显示提示符,或返回 true 以允许转换。

<Prompt message={location => ( location.pathname.startsWith('/app') ? true : `Are you sure you want to go to ${location.pathname}?` )}/>

when: bool

你可以一直渲染而不是在警示框出现之后才渲染一个 <Prompt> ,但是可以通过 when={true} 或 when={false} 来阻止或允许相应的导航。

<Prompt when={formIsHalfFilledOut} message="Are you sure?"/>

redux

就是VUEX

如果你不知道什么时候需要redux,就是你不需要它

当你遇到解决不了的事情自然会想起redux

redux和react-redux的区别

​ redux: js的状态管理器 createStore

​ react-redux: 为了在react中更容易的使用: connent provider

安装 npm i redux react-redux -S

文档链接

使用

index.js

import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import {createStore} from "redux"; import counter from "./store/conter" import {Provider} from "react-redux" // 创建store仓库 const store = createStore(counter) ReactDOM.render( {/*注意 需要从react-redux中引入provider组件并且包裹起来 注册store属性*/} <Provider store={store}> <App/> </Provider>, document.getElementById('root') );

app.js

import React from 'react'; // 引入connect建立与redux的链接 import {connect} from "react-redux" // 正常定义APP class App extends React.Component{ render() { console.log(this.props) return ( <div className="App"> <div> {this.props.counter} </div> <button onClick={this.props.onInc}>++</button> <button onClick={this.props.onDec}>--</button> </div> ); } } // 定义 dispatch 到props const mapDispatchToProps = dispatch => { return { onInc: () => {dispatch({type:"INC"})}, onDec: () => {dispatch({type:"DEC"})} } } // 定义 state 到props const mapStateToProps = (state)=>{ return { counter:state } } // connect接受 connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]) export default connect(mapStateToProps,mapDispatchToProps)(App);

如果不传第一个参数组件将不会监听 Redux store

如果不传第二个参数,默认情况下,dispatch 会注入到你的组件 props 中

第三个参数是一个回调函数

mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。

通过 bindActionCreators 绑定dispatch方法

react-redux-demo\src\store\action\conter.js

// 这里return的值应该是传递给action的 export function onInc() { return { type:"INC" } } export function onDec(){ return { type:"DEC" } }

react-redux-demo\src\App.js

import React from 'react'; import {connect} from "react-redux" import {bindActionCreators} from "redux"; // 引入 conter 中的两个function import * as conterAction from "./store/numAction.js/conter" class App extends React.Component{ render() { console.log(this.props) return ( <div className="App"> <div> {this.props.counter} </div> {/*使用*/} <button onClick={()=>this.props.counterActions.onInc()}>++</button> <button onClick={()=>this.props.counterActions.onDec()}>--</button> </div> ); } } const mapDispatchToProps = dispatch => { /* // props输出出来方法长成这个样子了 onDec:function () { return dispatch(actionCreator.apply(this, arguments)); } * */ return { // 传入 conterAction 上面引入的 counterActions:bindActionCreators(conterAction,dispatch) } } const mapStateToProps = (state)=>{ return { counter:state } } App = connect(mapStateToProps,mapDispatchToProps)(App) export default App;

传递参数

react-redux-demo\src\App.js

import React from 'react'; import {connect} from "react-redux" import {bindActionCreators} from "redux"; import * as conterAction from "./store/numAction.js/conter" class App extends React.Component{ render() { console.log(this.props) return ( <div className="App"> <div> {this.props.counter} </div> {/*传入参数*/} <button onClick={()=>this.props.counterActions.onInc(10)}>++</button> <button onClick={()=>this.props.counterActions.onDec(8)}>--</button> </div> ); } } const mapDispatchToProps = dispatch => { return { counterActions:bindActionCreators(conterAction,dispatch) } } const mapStateToProps = (state)=>{ return { counter:state } } App = connect(mapStateToProps,mapDispatchToProps)(App) export default App;

react-redux-demo\src\store\action\conter.js

// 接受参数 export function onInc(num) { return { type:"INC", num } } export function onDec(num){ return { type:"DEC", num } }

react-redux-demo\src\store\conter.js

const counter = (state = 0 , action) =>{ switch (action.type) { case "INC": return state+action.num; case "DEC": return state-action.num; default: return state; } } export default counter

**可见, react-redux-demo\src\store\action\conter.js return的对象传入到了 action中 **

合并state

react-redux-demo\src\store\index.js

// 合并多个state // 主要用到了redux 中的 combineReducers import {combineReducers} from "redux"; import conter from "./conter" import user from "./user" const RootState = combineReducers({ conter, user }) export default RootState

react-redux-demo\src\store\user.js

const counter = (state = { userName:"", img:"", list:[], age:0 } , action) =>{ switch (action.type) { case "CHANGEALL": return state = JSON.parse(JSON.stringify(action.data)); case "CHANGEONE": return state[action.key] = action.val; default: return state; } } export default counter

react-redux-demo\src\store\conter.js

const counter = (state = 0 , action) =>{ switch (action.type) { case "INC": return state+action.num; case "DEC": return state-action.num; default: return state; } } export default counter

react-redux-demo\src\index.js

import {createStore} from "redux"; import RootState from "./store"; // 创建store仓库 const store = createStore(RootState)

使用

react-redux-demo\src\App.js

import React from 'react'; import {connect} from "react-redux" import {bindActionCreators} from "redux"; import * as conterAction from "./store/numAction.js/conter" import * as userAction from "./store/numAction.js/user" class App extends React.Component{ componentDidMount() { // 调用 userchange方法 this.props.userActions.onChangeAll({ userName:"李大炮", img:"", list:["蕾姆","爱蜜莉雅"], age:20 }) } render() { console.log(this.props) return ( <div className="App"> <div> <p>{this.props.user.userName}</p> { this.props.user.list.map((item,index)=>{ return (<p key={index}> 他有 {item} 老婆 </p>) }) } </div> <div> {this.props.counter} </div> <button onClick={()=>this.props.counterActions.onInc(10)}>++</button> <button onClick={()=>this.props.counterActions.onDec(8)}>--</button> </div> ); } } const mapDispatchToProps = dispatch => { return { counterActions:bindActionCreators(conterAction,dispatch), userActions:bindActionCreators(userAction,dispatch) } } const mapStateToProps = (state)=>{ return { counter:state.conter, user:state.user } } App = connect(mapStateToProps,mapDispatchToProps)(App) export default App;

redux和中间件logger

// 创建中间件 const logger = (store) => { return next=> { return action => { console.log("dispatch", action) let result = next(action) console.log("next state ==> ", store.getState()) return result } } } // 创建第二个中间件 const err = store => next => action =>{ try { next(action) }catch (e) { console.log("错误"+ e) } } // 加载中间件 applyMiddleware(logger) 放在第三个参数的位置 const store = createStore(RootState,{},applyMiddleware(logger,err))

使用第三方中间件

这里用到两个中间件redux-logger和redux-chunk

redux-logger可以打印redux的状态

redux-chunk可以处理异步

react-redux-demo\src\index.js

import logger from "redux-logger" // 第三方中间件 输出redux的状态 import thunk from "redux-thunk" // 第三方中间件 处理异步操作 import {createStore, applyMiddleware} from "redux"; const store = createStore(RootState,{},applyMiddleware(logger,thunk))

chunk处理异步

react-redux-demo\src\store\action\conter.js

export function onInc(num) { return dispatch =>{ setTimeout(()=>{ dispatch({ type:"INC", num }) },1000) } }

处理网络请求

和上面的异步处理基本一致

redux调试

https://github.com/zalmoxisus/redux-devtools-extension

谷歌商店下载redux-devtools

npm install --save redux-devtools-extension import { createStore, applyMiddleware } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; const store = createStore(RootState,{},composeWithDevTools(applyMiddleware(logger,thunk)))

组件优化

当父组件发生变化的时候子组件也会一起发生变化,但是子组件并没有发生变化

这个时候有两种解决方案

让组件继承自React.PureComponent class Mouse extends React.PureComponent { // 与上面相同的代码...... }

React.PureComponent 只会进行浅对比, 当前组件内的props发生变化则刷新组件不发生变化则不刷新组件

手动对比 // shouldComponetUpdate 声明周期函数可以接受两个参数, nextProps和 nextState 这两个参数与this.props和this.state进行对比 然后返回true或者false可以允许更新或者阻止更新 shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; } return false; }

context(Minxi混入)

网上看起来比较好的文章

官方文档

如何使用Context

如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider),通常是一个父节点,另外是一个Context的消费者(Consumer),通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式。

对于父组件,也就是Context生产者,需要通过一个静态属性childContextTypes声明提供给子组件的Context对象的属性,并实现一个实例getChildContext方法,返回一个代表Context的纯对象 (plain object) 。

import React from 'react' import PropTypes from 'prop-types' class MiddleComponent extends React.Component { render () { return <ChildComponent /> } } class ParentComponent extends React.Component { // 声明Context对象属性 static childContextTypes = { propA: PropTypes.string, methodA: PropTypes.func } // 返回Context对象,方法名是约定好的 getChildContext () { return { propA: 'propA', methodA: () => 'methodA' } } render () { return <MiddleComponent /> } }

而对于Context的消费者,通过如下方式访问父组件提供的Context。

import React from 'react' import PropTypes from 'prop-types' class ChildComponent extends React.Component { // 声明需要使用的Context属性 static contextTypes = { propA: PropTypes.string } render () { const { propA, methodA } = this.context console.log(`context.propA = ${propA}`) // context.propA = propA console.log(`context.methodA = ${methodA}`) // context.methodA = undefined return ... } }

子组件需要通过一个静态属性contextTypes声明后,才能访问父组件Context对象的属性,否则,即使属性名没写错,拿到的对象也是undefined。

对于无状态子组件(Stateless Component),可以通过如下方式访问父组件的Context

import React from 'react' import PropTypes from 'prop-types' const ChildComponent = (props, context) => { const { propA } = context console.log(`context.propA = ${propA}`) // context.propA = propA return ... } ChildComponent.contextProps = { propA: PropTypes.string }

而在接下来的发行版本中,React对Context的API做了调整,更加明确了生产者消费者模式的使用方式。

import React from 'react'; import ReactDOM from 'react-dom'; const ThemeContext = React.createContext({ background: 'red', color: 'white' });

通过静态方法React.createContext()创建一个Context对象,这个Context对象包含两个组件,<Provider />和<Consumer />。

class App extends React.Component { render () { return ( <ThemeContext.Provider value={{background: 'green', color: 'white'}}> <Header /> </ThemeContext.Provider> ); } }

<Provider />的value相当于现在的getChildContext()。

class Header extends React.Component { render () { return ( <Title>Hello React Context API</Title> ); } } class Title extends React.Component { render () { return ( <ThemeContext.Consumer> {context => ( <h1 style={{background: context.background, color: context.color}}> {this.props.children} </h1> )} </ThemeContext.Consumer> ); } }

<Consumer />的children必须是一个函数,通过函数的参数获取<Provider />提供的Context。

可见,Context的新API更加贴近React的风格。

高阶组件

高阶组件需要符合几个条件

函数参数是一个组件返回值也是一个组件 // 高阶组件 import React from "react"; /* * 高阶组件 * 1. 函数 * 2. 参数是一个组件 * 3. 返回值也是一个组件 * */ function HeightComponent(Component) { return class extends React.Component{ render() { return ( <> 我! 高阶 ! 我! 牛逼 <Component {...this.props}/> </> ) } } } class MyData extends React.Component{ render() { return ( <div> MyData : {this.props.num} </div> ) } } class ParentCompontents extends React.Component{ render() { let HeightComponentDemo = HeightComponent(MyData) return ( <div> <HeightComponentDemo num={2233} /> </div> ) } } export default ParentCompontents

错误边界

主要利用一个生命周期函数componentDidCatch(error,errorInfo)

my-app\src\errorborder\index.jsx

import React from "react"; import ErrorsCom from "./errorscom"; import ErrorBorder from "./errorCatch" export default class ErrorBorderDemo extends React.Component{ render() { return ( <div> <ErrorBorder render={(error,errorInfo)=>{return <div><p>{error.message}</p><p>{errorInfo.componentStack}</p></div>}}> <ErrorsCom /> </ErrorBorder> </div> ) } }

my-app\src\errorborder\errorscom.jsx

import React from "react"; export default class ErrorsCom extends React.Component{ render() { return ( <div > { // 故意报错 null.map((item,index)=>{ return <span key={index}>{item}</span> }) } </div> ) } }

my-app\src\errorborder\errorCatch.jsx

import React from "react"; export default class ErrorBorder extends React.Component{ state = { hasError:false, error:null, errorInfo:null } // 子元素发生错误时触发 componentDidCatch(error, errorInfo) { this.setState({ hasError:true, error:error, errorInfo:errorInfo }) } render() { if(this.state.hasError){ return <div>{this.props.render(this.state.error,this.state.errorInfo)}</div> } return this.props.children } }

Hook

Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。稍后我们将学习其他 Hook。

什么时候我会用 Hook? 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook。

useState

在函数式组件中声明state属性

引入

import React, {useState} from 'react';

声明

import React, {useState} from 'react'; import * as actionBynum from "./actions/numAction" import RootState from "./stroe"; import {TemContext} from "./contText" function App(props) { const [num, setNum] = useState(0); // state const name = useState("张三"); // state // 相当于 componentDidMount 和 componentDidUpdate: useEffect(() => { document.title = `num is ${num}, title!` }) return ( <div className="App"> Hello <br/> {name[0]} {num} <button onClick={() => setNum(num + 1)}>123</button> <button onClick={() => name[1]("李四")}>setName</button> </div> ); } export default App;

调用 useState 方法的时候做了什么? 它定义一个 “state 变量”。我们的变量叫 count, 但是我们可以叫他任何名字,比如 banana。这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以我们传了 0 作为变量的初始 state。

如果我们想要在 state 中存储两个不同的变量,只需调用 useState() 两次即可。

useState 方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState() 的原因。这与 class 里面 this.state.count 和 this.setState 类似,唯一区别就是你需要成对的获取它们。

Effect Hook

可以使用多个Effect实现关注点分离(effect可以写多个)

Effect Hook相当于componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

引入

import React, { useState, useEffect } from 'react';

使用

import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 相当于 componentDidMount 和 componentDidUpdate: // 在rander渲染一次, 在updata渲染一次 useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }

useEffect 做了什么? 通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。

为什么在组件内部调用 useEffect? 将 useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。

useEffect 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。(我们稍后会谈到如何控制它。)你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕

在useEffect中返回一个函数会在组件卸载的时候调用, 如下:

useEffect(() => { interValId = setInterval(()=>{setCount(count+1)},1000) // 组件销毁时调用下方清除函数 return function cleanup() { clearInterval(interValId) }; });

提示: 通过跳过 Effect 进行性能优化

在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决:

componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${this.state.count} times`; } }

这是很常见的需求,所以它被内置到了 useEffect 的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:

useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 仅在 count 更改时更新

上面这个示例中,我们传入 [count] 作为第二个参数。这个参数是什么作用呢?如果 count 的值是 5,而且我们的组件重渲染的时候 count 还是等于 5,React 将对前一次渲染的 [5] 和后一次渲染的 [5] 进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个 effect,这就实现了性能的优化。

当渲染时,如果 count 的值更新成了 6,React 将会把前一次渲染时的数组 [5] 和这次渲染的数组 [6] 中的元素进行对比。这次因为 5 !== 6,React 就会再次调用 effect。如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 effect。

对于有清除操作的 effect 同样适用:

useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

未来版本,可能会在构建时自动添加第二个参数。

注意:

如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。参阅文档,了解更多关于如何处理函数以及数组频繁变化时的措施内容。

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

如果你传入了一个空数组([]),effect 内部的 props 和 state 就会一直拥有其初始值。尽管传入 [] 作为第二个参数更接近大家更熟悉的 componentDidMount 和 componentWillUnmount 思维模式,但我们有更好的方式来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

最新回复(0)