🎉 希望可以记录一些笔记,保持原有久笔记的前提下动态更新,方便对比复习
👉 值得学习的 Thinking In React 
 
前言 🙃 记录时间、版本的不同,代码风格会不同,最新的版本示例会是最上面一个然后用 新--- 和 旧--- 标识
TBR 标出的,是没看明白,还需细品的 😅
基本使用 安装 使用 vite 🌎 Getting Start 
先创建 vite 再选择 react
 
直接指定 react
1 2 3 4 5 6 7 npm create vite@latest my-react-app -- --template react #  yarn yarn create vite my-react-app --template react #  pnpm pnpm create vite my-react-app --template react 
 
非脚手架 1 npm install react react-dom 
 
通过<script>导入,注意导入顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <div  id ="root" > </div > <script  src ="./node_modules/react/umd/react.development.js" > </script > <script  src ="./node_modules/react-dom/umd/react-dom.development.js" > </script > <script >      const  title = React .createElement ("h1" , null , "hello react!" );      ReactDOM .render (title, document .getElementById ("root" )); </script > 
 
脚手架 React.createElement()和createRoot()的区别
前者:创建 React Element 
后者:创建一个 root-level 的容器来渲染 React 程序 React 18 
 
React 18 以前 下载
1 2 3 4 5 6 #  推荐 npx create-react-app myReact # npm init react-app myReact yarn create react-app myReact 
 
启动(要进入项目的根目录)
 
使用,通过 ES6 的 import 关键字
1 2 3 4 5 6 7 8 import  React  from  "react" ;import  React  from  "react-dom" ;const  title = React .createElement ("h1" , null , "hello react!" );ReactDOM .render (title, document .getElementById ("root" ));
 
React 18 以后 1 npm install react react-dom 
 
1 2 3 4 5 6 7 8 import  { createRoot } from  "react-dom/client" ;document .body .innerHTML  = '<div id="app"></div>' ;const  root = createRoot (document .getElementById ("app" ));root.render (<h1 > Hello, world</h1 >  ); 
 
或者是并不想清空当前 HTML 页面的内容,那就找一个元素当作容器来渲染当前的 React 组件
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html >   <head >      <title > My app</title >    </head >    <body >      <p > This paragraph is a part of HTML.</p >      <nav  id ="navigation" > </nav >      <p > This paragraph is also a part of HTML.</p >    </body >  </html > 
 
1 2 3 4 5 6 7 8 9 10 import  { createRoot } from  "react-dom/client" ;function  NavigationBar ( ) {     return  <h1 > Hello from React!</h1 >  ; } const  domNode = document .getElementById ("navigation" );const  root = createRoot (domNode);root.render (<NavigationBar  />  ); 
 
React 和 JSX jsx 是 JavaScript XML 的简写,在 JavaScript 文件中写 HTML-like 标签
因为jsx不是标准的ECMAScript语法,而是它的语法扩展,所以在普通环境是不可以使用的;要在脚手架中使用(因为脚手架中包含了babel,@babel/preset-react) 
 
JSX 和 React 是两个东西,前者是语法拓展,后者是一个 JavaScript 库。React 是使用了这种语法拓展。通常是一起使用的,但也可以分开使用。更多介绍 
 
语法规则 1. 单根节点 
原因:JSX 看起来像 HTML,但是是转换成原生的 JavaScript 对象,一个方法是不可以返回两个对象的,所以才需要包裹起来
 
1 2 3 4 5 6 7 8 9 <div>   <p > 123</p >    <p > 456</p >  </div> <>   <p > 123</p >    <p > 456</p >  </> 
 
这个空白的标签叫做 Fragment (片段 ??) 
完整样子:<Fragment></Fragment> 
向 Fragment 传 key ,不能使用短标签 <></>,需要从 react 导入 Fragment 然后 s 使用<Fragment key={yourKey}>...</Fragment> 
不会重置state,在从<><Child /></>到[<Child>]或反转的时候;<><><Child /></></>就会重置。关于重置 state,需要跳转看到state章节 
 
1 2 3 4 5 6 7 8 9 10 11 12 import  React  from  "react" ;import  React  from  "react-dom" ;const  title = (  <h1  className ="title" >      Hello Hi <span  />    </h1 >  ); ReactDOM .render (title, document .getElementById ("root" ));
 
class => className 、for => htmlFor,等等 
如果元素没有子节点,可以转为单标签:<span></span> => <span />,当然不转也行 
 
2. 必须要关闭标签 
…其实一直不知道标签还可以不用关闭的 🥲
1 2 3 4 5 6 7 <ul>   <li>12   <li>34   <li>56 </ul> <img> 
 
1 2 3 4 5 6 7 <ul >   <li > 12</li >    <li > 34</li >    <li > 56</li >  </ul > <img  /> 
 
3. 驼峰式属性名 
因为 JSX 要转换成 JavaScript 对象,所以,例如 HTML-like 中的样式类class改成了className,不然得和 JavaScript 类的关键字冲突
DOM 节点的className属性也是这个意思,避免与操作 DOM 的编程语言保留的class关键字冲突
对于样式类,class 是属于 HTML 的,而 className 是 DOM 属性
 
👉 这里查看所有的属性名 
由于历史原因aria-* 和 data-* 依然使用 - 而不是驼峰式。主要原因有 W3C 定的一些 HTML 规范,然后 React 也遵循这些规范以适配很多库、开发工具、不同技术等等
 
aria 规范: Accessible Rich Internet Applications,提供一组属性增强 web 应用程序的可访问性。 
 
使用 js 表达式 (JSX 的花括号) 
❗ 花括号外面是不需要加 双引号或者单引号 的;花括号里面如果是字符串   return 语句中 JS 表达式要写在花括号里面
 
1 2 3 4 5 export  default  function  Avatar ( ) {  const  avatar = "https://i.imgur.com/7vQD0fPs.jpg" ;   const  description = "Gregorio Y. Zara" ;   return  <img  className ="avatar"  src ={avatar}  alt ={description}  />  ; } 
 
1 2 3 const  name = "Jerry" ; const  title = <h1 > {name}</h1 >  ;ReactDOM .render (title, document .getElementById ("root" ));
 
在{}中可以使用任意的合法的JavaScript表达式,不过也有例外
1 2 3 4 5 6 7 8 9 10 11 12 const  hello  = ( ) => "hello" ;const  myDiv = <div > 我是一个div</div >  ;const  title = (  <div >      <p > {1 + 1}</p >      <p > {1 < 2 ? "对呀" : "不对"}</p >      <p > {hello()}</p >      {div}   </div >  ); ReactDOM .render (title, document .getElementById ("root" ));
 
jsx 自身也是表达式,所以{div}也适用 
<p>{ {a: "我是a"} }</p>,这种对象是不行 的,但是在style样式中又可以使用 
在里面使用语句也是不行 的:if、for 这些 
 
JSX 使用两个花括号的场景 
CSS:<ul style={{backgroundColor: 'black', color: 'pink'}}>
 
JSX 传递对象:person={{ name: "Hedy Lamarr", inventions: 5 }} 
 
条件渲染 1 2 3 4 5 6 7 8 9 10 11 12 function  Item ({ name, isPacked } ) {  return  <li  className ="item" > {isPacked ? name + "✔" : name}</li >  ; } export  default  function  Pane ( ) {  return  (     <div >        <Item  name ="mike"  isPacked ={false} > </Item >        <Item  name ="amy"  isPacked ={true} > </Item >      </div >    ); } 
 
使用逻辑与简化条件判断
1 2 3 4 5 return  (  <li  className ="item" >      {name} {isPacked && "✔"}   </li >  ); 
 
React considers false as a “hole” in the JSX tree,像undefined和null一样不渲染东西 
可用多个花括号 
 
注意 && 左边有数字,因为如果是 0  的话,会被认为是false;可以加个前提判断当左边大于 0 
 
如果要简化返回语句,或者有个默认返回,可以使用结合使用变量和 JSX 
1 2 3 4 5 6 7 8 function  Item ({ name, isPacked } ) {  let  content = name;   if  (isPacked) {          content = <del > {name + "✔"}</del >  ;   }   return  <li  className ="item" > {content}</li >  ; } 
 
列表渲染 
箭头函数=>后面隐式返回,不用加上return,但是只返回一行;返回多行=>需要加上{}和return
 
如果要渲染一组数据,应该使用数组的map()方法
1 2 3 4 5 6 7 8 9 10 11 const  songs = [  { id : 1 , name : "我很快乐"  },   { id : 2 , name : "你很快乐"  },   { id : 3 , name : "他很快乐"  }, ]; export  default  function  SongList ( ) {     const  songItems = songs.map (item  =>  <li  key ={item.id} > {item.name}</li >  );   return  <ul > {songItems}</ul >  ; } 
 
不要使用Math.random()生成 key,key 除了标识当前 DOM,还会有缓存作用,数据不变化的不会重新渲染以使得渲染更快,如果用了随机数,所有的 DOM 都会在数据更新时重新渲染   key 在props是获取不到的
 
想要渲染多个 DOM 节点但是又不想在外面包一个多余的节点,例如
1 2 3 4 5 6 const  listItems = people.map (person  =>  (  <div  key ={person.id} >      <h1 > {person.name}</h1 >      <p > {person.bio}</p >    </div >  )); 
 
不想,要这个外面的<div>,可用<Fragment>代替,在 DOM 中 Fragments 不会出现
1 2 3 4 5 6 7 8 9 import  { Fragment  } from  "react" ;const  listItems = people.map (person  =>  (  <Fragment  key ={person.id} >      <h1 > {person.name}</h1 >      <p > {person.bio}</p >    </Fragment >  )); 
 
记得要事先导入:
不能使用 <></>,因为它不能传递 key
 
样式处理 行内样式:style(不推荐),使用样式时可以在{}中使用对象
1 2 3 const  list = (  <h1  style ={{  color:  "red ", backgroundColor:  "yellow " }}> JSX行内样式</h1 >  ); 
 
类名:className
1 2 3 4 5 .title  {  color : "red" ;   background-color : "yellow" ; } 
 
1 import  "index.css" const  list = ( <h1  className ="title" > JSX类样式</h1 >   )
 
小结:React 是利用 JavaScript 语言自身来编写界面,而不是像 Vue 一样通过增强 HTML 的功能。
 
组件基础 
一个页面可以全部都是 React 组件。 
a React component is a JavaScript function that you can sprinkle with markup 
 
两种创建方式 
React 16.8 以后 推荐创建组件的方法不再是 类组件  ,而是 函数组件 
 
函数的方式和类的方式
函数组件 
使用function关键字 
函数名称大写 开头 
函数组件必须有返回值(返回null表示不渲染内容)
 
不要在组件里面再定义其他组件 
 
1 2 3 4 5 6 function  Hello ( ) {  return  <div > 这是一个函数组件</div >  ; } ReactDOM .render (<Hello  />  , document .getElementById ("root" ));
 
1 2 3 4 5 6 7 export  default  function  Profile ( ) {  return  (     <div >        <img  src ="https://i.imgur.com/MK3eW3Am.jpg"  alt ="Katherine Johnson"  />      </div >    ); } 
 
虽然里面有src 、alt ,但是实则为 JavaScript,这种写法叫 
React 会以大小写来区分 HTML 标签和 React 组件 
 
类组件 
使用class关键字 
类名要大写开头,并且继承于React.Component 
必须有render()方法,并且这个方法要有返回值 
 
1 2 3 4 5 6 7 class  Hello  extends  React.Component  {    render  (     	return  ( <div > 这是一个类组件</div >   )     ) } ReactDOM .render (<Hello  />  , document .getElementById ("root" ))
 
组件的导入导出 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  NavMenu  from  "./navMenu.js" ;function  HeaderTitle ( ) {  return  <h1 > </h1 >  ; } export  default  function  Header ( ) {  return  (     <div >        <NavMenu  />        <HeaderTitle  />      </div >    ); } 
 
1 2 3 4 5 6 7 8 9 10 11 import  React  from  "react" class  Hello  extends  React.Component  {    render  (     	return  ( <div > 这是一个独立文件组件</div >   )     ) } export  default  Hello 
 
1 2 3 4 5 6 7 8 import  React  from  "react" ;import  React  from  "react-dom" ;import  Hello  from  "./Hello.js" ;ReactDOM .render (<Hello  />  , document .getElementById ("root" ));
 
默认与命名导出导入 
语法 
导出 
导入 
 
 
默认 
export default function Button() {} 
import Button from './Button.js'; 
 
命名 
export function Button() {} 
import { Button } from './Button.js'; 
 
默认导出,导入的时候,导入名字随便写 
命名的时候,名字需要对应 
 
虽然默认导出export default () => {}没问题,但是不推荐没有名字
事件处理 基本使用 
on+事件名称={事件处理程序},onClick={()=>{}} 
驼峰式命名 
 
事件处理函数必须是传递 而不是调用 ,就是说不用在函数后面加上括号
 
给组件添加事件处理:定义一个函数,然后作为props 传递给<button>
这个函数一般在当前这个组件里面 
以handle开头:onClick={handleClick}, onMouseEnter={handleMouseEnter} 
 
1 2 3 4 5 6 7 export  default  function  Button ( ) {  function  handleClick ( ) {     alert ("You clicked me!" );   }   return  <button  onClick ={handleClick} > 点击</button >  ; } 
 
1 2 3 4 5 6 7 8 class  Button  extends  React.Component  {  handleClick ( ) {     console .log ("触发单击事件" );   }   render ( ) {     return  <button  onClick ={this.handleClick} > 点击</button >  ;   } } 
 
事件对象 
事件处理器会捕获到子组件可能会有的事件:称为冒泡或者传播;在事件发生的地方开始,然后顺着组件树往上传递。比如子组件和父组件都有点击事件。 
 
所有事件都会冒泡,除了onScroll,只在使用的地方促发
 
事件处理函数仅有的一个参数就是事件对象 ,一般用e来表示
e.stopPropagation() 阻止冒泡 
父组件添加onClickCapture={()=>{ /* ... */}} 捕获子组件事件 
 
1 2 3 4 5 <div onClickCapture={() =>  {}}>   <button  onClick ={e  =>  e.stopPropagation()} />   <button  onClick ={e  =>  e.stopPropagation()} /> </div> 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 class  Button  extends  React.Component  {  handleClick (e ) {          e.preventDefault ();   }   render ( ) {     return  (       <a  href ="www.baidu.com"  onClick ={this.handleClick} >          去百度       </a >      );   } } 
 
事件处理函数读取 props 和作为 props 传递 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function  Button ({ onClick, children } ) {  return  <button  onClick ={onClick} > {children}</button >  ; } function  PlayButton ({ movieName } ) {     function  handlePlayClick ( ) {          alert (`Playing ${movieName} !` );   }      return  <Button  onClick ={handlePlayClick} > Play "{movieName}"</Button >  ; } export  default  function  Toolbar ( ) {  return  (     <div >        <PlayButton  movieName ="Kiki's Delivery Service"  />      </div >    ); } 
 
自定义事件处理函数 props 名字 
作为 props 传递时使用的名字 
上面的handlePlayClick作为 props 传递的时候使用的是onClick,算是使用了默认名字 
 
一般以on开头,然后驼峰式命名
1 2 3 4 5 6 7 8 9 10 11 12 function  Button ({ onSmash, children } ) {  return  <button  onClick ={onSmash} > {children}</button >  ; } export  default  function  App ( ) {  return  (     <div >        <Button  onSmash ={()  =>  alert("Playing!")}>Play Movie</Button >        <Button  onSmash ={()  =>  alert("Uploading!")}>Upload Image</Button >      </div >    ); } 
 
对于原生的 HTML 元素,要尽量使用对应功能的元素其对应的事件。比如点击事件会用到<button>而不是div
 
state 随着时间改变的数据叫 state ,对于对象和数组,react 推荐它们的使用是不可变的(immutable),要想更新,就创建一个新的
有状态和无状态组件 
无状态组件:函数组件;有状态组件:类组件 React 16.8 
状态(state)负责数据 
函数组件没状态,可以用于展示数据(静态) React 16.8 
类组件有自己状态,可以用于更新界面(动态)React 16.8 后不再是主推的 
 
state 基本使用(新) 1 2 3 4 5 6 7 8 import  { useState } from  "react" ;const  [index, setIndex] = useState (0 );function  handleClick ( ) {  setIndex (index + 1 ); } 
 
useState 返回两个东西通过解构获得,一个是这个值,另一个是更新这个值二点方法 
名字随便起,但习惯使用 名字 和 set名字 
每个组件里的 state 都是独立的 
 
use开头的Hooks 方法,只可在组件的top level 执行,不可再条件判断、列表循环中使用
 
更改 对象  类型的 state 直接更新对象里的属性是不会触发页面更新的
1 2 3 4 5 6 const  [position, setPosition] = useState ({ x : 0 , y : 0  });position.x  = e.clientX ; position.y  = e.clientY ; 
 
虽然在某些情况这样做会有效,但是并不推荐。所以要使用setPosition传递一个新的对象过去,然后组件重新渲染
重点是使用setXXX这个函数,不管要更新的值以什么形式变化,比如
1 2 3 4 5 6 7 8 const  nextPosition = {};nextPosition.x  = e.clientX ; nextPosition.y  = e.clientY ; setPosition (nextPosition); setPosition ({ x : e.clientX , y : e.clientY  });
 
不过这样会有个新的问题,就是只是想要改变某一个属性的值,不想要改变其他值。
使用展开运算符将不需要改变的对象属性复制到新的对象
1 2 3 4 5 6 7 8 9 10 11 12 setPerson ({  firstName : e.target .value ,   lastName : person.lastName ,   email : person.email , }); setPerson ({  ...person,    firstName : e.target .value ,  }); 
 
展开运算符仅在对象的第一层起作用,如果要复制更深层的,得多次使用展开运算符
1 2 3 4 5 6 7 8 const  [person, setPerson] = useState ({  name : "Niki de Saint Phalle" ,   artwork : {     title : "Blue Nana" ,     city : "Hamburg" ,     image : "https://i.imgur.com/Sd1AgUOm.jpg" ,   }, }); 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 const  nextArtwork = { ...person.artwork , city : "New Delhi"  };const  nextPerson = { ...person, artwork : nextArtwork };setPerson (nextPerson);setPerson ({  ...person,    artwork : {          ...person.artwork ,      city : "New Delhi" ,    }, }); 
 
❔ 其他情况:更改 obj3.artwork,obj1 和 obj2.artwork 也会改变,因为它们是相同的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let  obj1 = {  title : "Blue Nana" ,   city : "Hamburg" ,   image : "https://i.imgur.com/Sd1AgUOm.jpg" , }; let  obj2 = {  name : "Niki de Saint Phalle" ,   artwork : obj1, }; let  obj3 = {  name : "Copycat" ,   artwork : obj1, }; 
 
更新 数组  类型的 state 
同样推荐不可变(immutable),所以不要使用arr[0] = "qaq"来修改其中的值 
同样也不推荐使用pop()、push()等方法来改变它 
推荐从旧数组的基础上创建新的数组,并且使用不会改变旧数组的数组方法,例如filter()、map() 
 
React 中 state 中数组的操作方法推荐(避免使用该百年数组的,推荐使用返回新数组的):
 
避免 
推荐 
 
 
添加 
push, unshift 
concat, […arr] 
 
删除 
pop, shift, splice 
filter, slice 
 
替换 
splice, arr[i]=xx 
map 
 
排序 
reverse, sort 
先复制这个数组 
 
添加内容 :和对象一样,使用扩展运算符来复制以达到改变某一个值的目的
1 2 3 4 5 6 7 8 9 10 const  [artists, setArtists] = useState ([]);setArtists (     [          ...artists,      { id : nextId++, name : name },    ] ); 
 
要改变新插入值的位置,将新插入的值这行放到扩展运算的上面就行 
 
删除内容 :最简单的就是过滤掉这个不需要的,或者直接创建个新数组的不包含这个要删除的内容
替换内容 :在原有的基础上创建一个新的数组,使用map,如果符合,改变这个值然后返回,不符合的返回原来的样子
1 2 3 4 5 6 7 8 9 10 11 12 const  [counters, setCounters] = useState ([xx, xxx]);const  nextCounters = counters.map ((c, i ) =>  {  if  (i === index) {          return  c + 1 ;   } else  {          return  c;   } }); setCounters (nextCounters);
 
插入内容 :确定要添加的位置,然后使用slice分割数组,将要添加的放到两个切片中间
1 2 3 4 5 6 7 8 9 10 11 12 13 const  [artists, setArtists] = useState ([xxx, xx, x]);const  insertAt = 1 ; const  nextArtists = [     ...artists.slice (0 , insertAt),      { id : nextId++, name : name },      ...artists.slice (insertAt), ]; setArtists (nextArtists);
 
其他操作 :比如 反转 ,排序 ,js 方法会改变旧的数组,所以要先复制出一个数组然后再做出改变,如nextList.sort(),nextList[0] = {name: "zs", age: 18}
对于数组 list  和 nextList ,虽然不是相同的数组,但是list[0]和nextList[0]指向的是相同的对象,所以直接nextList[0].age=19这样的还是不推荐的,因为这是浅拷贝,是直接改掉了对象里面的东西
 
👉 更新数组里面的对象 
对象其实并不是再数组里面的,只是在代码这里看起来是在里面;但事实上当使用数组时(虽然已经使用拓展运算符复制出不同的数组),尝试改变其中数组内元素的值,另外的引用也会跟着改变,因为数组它内容本身还是和旧数组一样,只是在新的数组里面呆着罢了
所以改变数组里面你的对象,可以通过使用map找出要改变的对象,然后使用更新对象的方法更新目标对象
state 基本使用 
一个对象,是组件内部私有 的数据,只能在组件内部使用 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class  Hello  extends  React.component  {  construtor ( ) {          super ();          this .state  = {       count : 0 ,     };   }   render ( ) {     return  (       <div >          <h1 > 计数器:{this.state.count}</h1 >          <button             onClick ={()  =>  {            this.setState({ count: this.state.count + 1 });           }}         >           +1         </button >        </div >      );   } } 
 
❗ 注:不能直接修改 state 中的值:this.state.count++,这样是错的 
 
上面的语法有个简化版的,去掉了构造器和 super
1 2 3 4 5 6 7 8 9 10 11 12 class  Hello  extends  React.component  {  state = {     count : 0 ,   };   render ( ) {     return  (       <div >          <h1 > 计数器:{this.state.count}</h1 >        </div >      );   } } 
 
this 指向问题解决 1 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class  Hello  extends  React.component  {  construtor ( ) {     super ();     this .state  = { count : 0  };   }      add ( ) {     this .setState ({       count : this .state .count  + 1 ,     });   }   render ( ) {     return  (       <div >          <h1 > 计数器:{this.state.count}</h1 >          <button  onClick ={()  =>  this.add()}>+1</button >        </div >      );   } } 
 
❗ 注:在<button>中调用时,函数后面要 加上() 
 
this 指向问题解决 2 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class  Hello  extends  React.component  {  construtor ( ) {     super ();          this .add  = this .add .bind (this );     this .state  = {       count : 0 ,     };   }   add ( ) {     this .setState ({       count : this .state .count  + 1 ,     });   }   render ( ) {     return  (       <div >          <h1 > 计数器:{this.state.count}</h1 >          <button  onClick ={this.add} > +1</button >        </div >      );   } } 
 
❗ 注:在<button>中调用时,这里已经不是函数调用,所以函数后面不用 加上() 
 
this 指向问题解决 3 基于上面的内容,只需要修改add()
1 2 3 4 5 add = () =>  {   this .setState ({     count : this .state .count  + 1 ,   }); }; 
 
表单处理 受控组件 
HTML 中的状态(数据)是元素自己控制的,但是在 React 中要在 state 中,并且只能通过 setState 来修改 
解决这个冲突,React 将state和元素的value绑定在一起 
受控组件,就是其值是受到 React 控制的 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class  App  extends  React.Component  {     state = {     txt : "" ,     city : "gz" ,     isCheck : false ,   };   handleChange = e  =>  {     this .setState ({       txt : e.target .value ,     });   };   handleCity = e  =>  {     this .setState ({       city : e.target .value ,     });   };   handleChecked = e  =>  {     this .setState ({       isCHecked : e.target .checked ,     });   };   render ( ) {     return  (       <div >          <input             type ="text"            value ={this.state.txt}            onChange ={this.handleChange}          />         <select  value ={this.state.city}  onChange ={this.handleCity} >            <option  value ="sh" > 上海</option >            <option  value ="bj" > 北京</option >            <option  value ="gz" > 广州</option >          </select >          <input             type ="checkbox"            checked ={this.state.isChecked}            onChange ={this.handleChecked}          />       </div >      );   } } 
 
🌝 可以对上面的代码进行优化
给表单元素添加name属性,用来区分不同的表单元素,名称与对应的state相同。 
根据表单元素类型获取对应的值。(value、checked) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class  App  extends  React.Component  {  state = {     txt : "" ,     city : "gz" ,     isCheck : false ,   };   handleForm = e  =>  {          const  target = e.target ;          const  value = target.type  === "checkbox"  ? target.checked  : target.value ;          const  name = target.name ;          this .setState ({       [name]: value,     });   };   render ( ) {     return  (       <div >          <input             type ="text"            name ="txt"            value ={this.state.txt}            onChange ={this.handleForm}          />         <select  name ="city"  value ={this.state.city}  onChange ={this.handleForm} >            <option  value ="sh" > 上海</option >            <option  value ="bj" > 北京</option >            <option  value ="gz" > 广州</option >          </select >          <input             name ="isChecked"            type ="checkbox"            checked ={this.state.isChecked}            onChange ={this.handleForm}          />       </div >      );   } } 
 
非受控组件 
通过ref,使用原生 DOM 来获取表单元素的值
 
创建一个 ref 对象 
 
 
将创建好的 ref 对象放到目标元素中 
 
1 <input type="text"  ref={this .txtRef } /> 
 
通过 ref 获取到目标元素的值 
 
1 console .log (this .txtRef .current .value );
 
react 中不推荐直接操作 DOM
 
保持组件整洁 1: 组件就像是公式,不会有意料之外的结果,比如
1 2 3 function  double (number ) {  return  2  * number; } 
 
2: 保持为一个纯函数,只管自己的事,在调用这个组件之前,不会改变存在的变量或对象
❌ 不好的示例:每使用一次组件guest的值增加了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let  guest = 0 ;function  Cup ( ) {     guest = guest + 1 ;   return  <h2 > Tea cup for guest #{guest}</h2 >  ; } export  default  function  TeaSet ( ) {  return  (     <>        <Cup  />        <Cup  />        <Cup  />      </>    ); } 
 
正确做法是通过props将值传进去 
或者将数据在TeaSet中操作 
 
3: “副作用” 不需要保持整洁?
就是一些 事件处理器 不需要 这样,因为渲染的时候它们并没有执行,而是在等时间触发。所以在这可以改变一些用户的输入、响应等
TBR: Keeping Components Pure 
组件进阶 props 
接收传递给组件的数据 
传递数据:给组件标签添加属性 
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据 
props只可读 
 
props 是动态的,并不是组件被创建之后就写死了的
但是 props 是不可变对象 
当组件要改变它的 props,首先会向父组件请求所需数据来传递不同的 props 
旧的 props 被丢弃,随后被 JavaScript 引擎回收这个 props 占的内存 
 
不要直接改变 props 的值,需要使用 set state
 
新---
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export  default  function  Profile ( ) {  return  (     <Avatar  person ={{  name:  "Lin  Lanying ", imageId:  "1bX5QH6 " }} size ={100}  />    ); } function  Avatar ({ person, size } ) {  return  (     <img         className ="avatar"        src ={getImageUrl(person)}        alt ={person.name}        width ={size}        height ={size}      />   ); } function  getImageUrl ( ) {   } 
 
指定默认值 
和 js 一样,在参数那指定就行
1 2 3 function  Avatar ({ person, size = 10  } ) {   } 
 
没传递 size  的时候会使用默认值,或者是传递 size={undefined} 
size={null}或者size={0},不会使用默认值 
 
更简洁的传递 props 
前提是要想好使用场景?比如父组件就是需要解构,那没办法
 
不简洁的
1 2 3 4 5 6 7 8 9 10 11 12 function  Profile ({ person, size, isSepia, thickBorder } ) {  return  (     <div  className ="card" >        <Avatar           person ={person}          size ={size}          isSepia ={isSepia}          thickBorder ={thickBorder}        />     </div >    ); } 
 
这里父组件接收到的 props 然后又原封不动再写一次传给子组件,有点麻烦 
 
可以这样
1 2 3 4 5 6 7 function  Profile (props ) {  return  (     <div  className ="card" >        <Avatar  {...props } />      </div >    ); } 
 
旧---
函数组件
1 2 3 4 5 6 7 8 9 10 11 const  Hello  = props => {     return  (     <div >        <h1 > {props.name}</h1 >      </div >    ); }; ReactDOM .render (<Hello  name ="tom"  age ={10}  />  , document .getElementById ("root" ));
 
类组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class  Hello  extends  React.Component  {     constructor (props ) {     super (props);   }      render ( ) {     return  (       <div >          <h1 > {this.props.age}</h1 >          {tag}       </div >      );   } } ReactDOM .render (  <Hello       name ="tom"      age ={10}      fn ={()  =>  {      console.log("这是一个函数");     }}     tag={<p > 这是一个p标签</p > }   /> ,  document .getElementById ("root" ) ); 
 
传递非字符串的内容要使用{}包起来
 
props 深入 children 属性 
通过props.children获得
1 2 3 4 5 6 7 8 9 10 11 12 13 import  Avatar  from  "./Avatar.js" ;function  Card ({ children } ) {  return  <div  className ="card" > {children}</div >  ; } export  default  function  Profile ( ) {  return  (     <Card >        <Avatar  size ={100}  />      </Card >    ); } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 const  App  = props => {  return  (     <div >        <h1 > 组件标签子节点</h1 >        {props.children}     </div >    ); }; ReactDOM .render (<App  />  , document .getElementById ("root" ));ReactDOM .render (<App > 我是子节点</App >  , document .getElementById ("root" ));
 
子节点可以为任意的jsx表达式、组件、函数 
如果是函数,直接使用props.children(),外面不用加 {} 
 
props 校验 
在使用之前,先安装prop-types
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  PropTypes  from  "prop-types" ;const  App  = props => {  const  arr = props.colors ;   const  list = arr.map ((item, index ) =>  <li > {item}</li >  ); }; App .propTypes  = {     colors : PropTypes .array , }; ReactDOM .render (  <App  colors ={[ "red ", "yellow "]} />  ,   document .getElementById ("root" ) ); 
 
⚠ 约束规则:
常见的约束类型:array、bool、func、number、object、string 
React 元素类型:element 
必填项:isRequired(在约束规则后点使用) 
特定结构的对象:shape({ }) 
…… 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import  PropTypes  from  "prop-types" ;const  App  = props => {  return  <div > </div >  ; }; App .propTypes  = {  a : PropTypes .number ,   fn : PropTypes .func .isRequired ,   tag : PropTypes .element ,   filter : PropTypes .shape ({     area : PropTypes .string ,     price : PropTypes .number ,   }), }; ReactDOM .render (<App  fn ={()  =>  {}} /> , document .getElementById ("root" ));
 
props 默认值 1 2 3 4 App .defaultProps  = {  pageSize : 10 , }; 
 
render props 
用于组件复用 
复用 state 和操作 state 的方法 
 
render 
这个 render 名字是随便取的
 
使用组件时拿到组件内部的 props,可以给组件提供的一个函数,然后通过函数的参数来获取。<Mouse render={ (mouse) => {} } />,然后函数的返回值作为页面要渲染的结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import  img from  "./image/cat.jpg" ;class  Mouse  extends  React.Component  {  state = {     x : 0 ,     y : 0 ,   };      handleMouseMove = e  =>  {     this .setState ({       x : e.clientX ,       y : e.clientY ,     });   };      componentDidMount ( ) {     window .addEventListener ("mousemove" , this .handleMouseMove );   }      render ( ) {     return  this .props .render (this .state );   } } class  App  extends  React.Component  {  render ( ) {     reutrn (       <div >          <h1 > render props 模式</h1 >          <Mouse             render ={mouse  =>  {            return (               <p >                  鼠标位置:{mouse.x} {mouse.y}               </p >              );           }}         />         {/* 复用一个<Mouse  />  */}         <Mouse             render ={mouse  =>  {            return (               <img                   src ={img}                  alt ="猫"                  style ={{                    position:  "absolute ",                   top:  "mouse.y ",                   left:  "mouse.x ",                 }}               />             );           }}         />       </div >      );   } } 
 
在class Mouse中,组件是要返回内容的,但是在复用组件的情况下,class Mouse并不知道要返回什么,所以在使用<Mouse />时候提供的要渲染的内容,然后在this.props.render中接收 
意思就是声明<Mouse />,和使用<Mouse /> 
 
children 取代 render 🎡 格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 render ( ) {    return  this .props .children (this .state ) } <Mouse > 	{         mouse  =>  {             return  ( <p > 鼠标位置:{mouse.x} {mouse.y}</p >   )         }     } </Mouse > 
 
代码优化 校验
1 2 3 Mouse .propTypes  = {  children : Proptypes .func .isRequired , }; 
 
移除mousemove事件
1 2 3 componentWillUnmount ( ) {    windows.removeEventListener ("mousemove" , this .handleMouseMove ) } 
 
组件之间的通讯 父组件传给子组件 
父组件提供要传递的state数据 
子组件标签添加属性,值为state中的数据 
子组件通过props接收父组件中传递的数据 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class  Parent  extends  React.Component  {  constructor ( ) {     super ();     this .state  = {       lastName : "tom" ,     };   }   render ( ) {     return  (       <div >          父组件:         <Child  name ={this.state.lastName}  />        </div >      );   } } const  Child  = props => {  return  (     <div >        <p > 子组件,接收父组件传递的数据。{props.name}</p >      </div >    ); }; ReactDOM .render (<Parent  />  , document .getElementById ("root" ));
 
子组件传给父组件 
父组件提供回调函数,用来接收数据(谁要接收数据,谁就提供回调函数 ) 
将改函数作为属性的值,传递给子组件 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class  Parent  extends  React.Component  {  constructor ( ) {     super ();     this .state  = {       parentMsg : "" ,     };   }   getChildMsg = data  =>  {     console .log ("接收子组件传递过来的数据" , data);     this .setState ({       parentMsg : data,     });   };   render ( ) {     return  (       <div >          父组件:给子组件提供了函数         <Child  getMsg ={this.getChildMsg}  />          {this.state.parentMsg}       </div >      );   } } class  Child  extends  React.Component  {  constructor ( ) {     super ();     this .state  = {       msg : "你好" ,     };   }   handleClick = () =>  {          this .props .getMsg (this .state .msg );   };   render ( ) {     return  (       <div >          子组件:<button  onClick ={this.handleClick} > 给父组件传递数据</button >        </div >      );   } } 
 
兄弟组件传值 
将要共享的数据提升到最近的公共度组件中 
公共父组件要做的事:提供共享数据、提供操作共享数据的方法 
要传值的子组件通过props接收数据或是接收操作数据的方法 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class  Counter  extends  React.Component  {     state = {     count : 0 ,   };      add = () =>  {     this .setState ({       count : this .state .count  + 1 ,     });   };   render ( ) {     return  (       <div >          <Child1  count ={this.state.count}  />          <Child2  add ={this.add}  />        </div >      );   } } const  Child1  = props => {  return  <h1 > 计数器:{props.count}</h1 >  ; }; const  Child2  = props => {  return  <button  onClick ={()  =>  props.add()}>+1</button >  ; }; ReactDOM .render (<Counter  />  , document .getElementById ("root" ));
 
Context 
使用React.createContext()创建Provider和Consumer两个组件 
使用<Provider>将父组件包起来 
设置value属性,表示要传递的值 
使用<Consumer>组件接收数据 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const  { Provider , Consumer  } = React .createContext ();class  App  extends  React.Component  {  render ( ) {     return  (       <Provider  value ="pink" >          <div >            <Node  />          </div >        </Provider >      );   } } const  Node  = props => {  return  (     <div >        <SubNode  />      </div >    ); }; const  SubNode  = props => {  return  (     <div >        <SubNode  />      </div >    ); }; const  Child  = props => {  return  (     <div >        <Consumer > {data => <span > 我是子节点 {data}</span > }</Consumer >      </div >    ); }; 
 
组件的生命周期 📚 详细指导 
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程 
只有类组件才有生命周期 
 
💤 不常用的生命周期:点我 😁
创建时 
更新时 
卸载时 
 
 
constructor、更新 DOM 和 refs 时、componentDidMount 
constructor、更新 DOM 和 refs 时、render、componentDidUpdate 
componentWillUnmount 
 
创建时 🚲 执行顺序:
1 2 3 graph LR A(constructor) -->B(render) B-->C(componentDidMount) 
 
钩子函数 
触发时机 
作用 
 
 
constructor 
创建组件时,最先执行 
1. 初始化 state、2. 为事件处理程序绑定 this 
 
render 
每次组件渲染都会触发 
渲染界面(不能调用setState() ) 
 
componentDidMount 
组件挂载(完成 DOM 渲染)后 
1. 发送网络请求、2. DOM 操作 
 
componentDidMount是在render()、constructor()外面直接函数调用的,是类的一个成员 
 
更新时 
导致组件更新的情况:new props、setState()、forceUpdate() 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class  App  extends  React.Component  {    constructor (props ) {         super (props)         this .state  = {             count : 0          }     }     handleClick = () =>  {                  this .setState ({             count : this .state .count  + 1          })                       }     render ( ) {         console .log ("生命周期钩子函数:render" )         return  (         	<div >              	<Counter  count ={this.state.count}  />                  <button  onClick ={this.handleClick} > 打豆豆</button >              </div >          )     } } class  Counter  extends  React.Component  {    render ( ) {         console .log ("子组件---生命周期钩子函数:render" )         return  (         	<h1 > 统计打豆豆的次数:{this.props.count}</h1 >          )     } } componentDidUpdate (prevProps ) {                   if  (prevProps.count  !== this .props ){         this .setState ({                      })         }     console .log ("componentDidUpdate" ) } 
 
🚙 执行顺序:
1 2 graph LR A(render) --> B(componentDidUpdate) 
 
钩子函数 
触发时机 
作用 
 
 
render 
每次组件渲染都会触发 
渲染界面 
 
componentDidUpdate 
组件更新(完成 DOM 渲染)后 
1. 发送网络请求、2. DOM 操作、如果要setState(),必须放在一个if条件中  
 
如果没有在 if 里面调用,就会造成递归更新(执行太多次后停下来报错) 
 
卸载时 
钩子函数 
触发时机 
作用 
 
 
componentWillUnmount 
组件卸载(从页面消失) 
执行清理工作(如:清理定时器) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <div>     {         this .state .count  > 3          ? <span > 豆豆被打GG了</span >          : <Counter  count ={this.state.count}  />      }    <button onClick={this .handleClick }>打豆豆</button> </div> componentWillUnmount ( ) {    console .log ("豆豆被GG,我被触发了" ) } 
 
高阶组件 
也是用于组件的复用,包装组件,增强组件的功能 
HOC,Higher-Order Component,是一个函数,接收要包装的组件,返回增强后的组件 
 
1 const  EnhancedComponent  = withHOC (WrappedComponent );
 
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过 prop 将复用的状态传递给被包装组件
1 2 3 4 5 class  Mouse  extends  React.Component  {  render ( ) {     return  <WrappedComponent  {...this.state } />  ;   } } 
 
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 function  withMouse (WrappedComponent ) {     class  Mouse  extends  React.Component  {          state = {       x : 0 ,       y : 0 ,     };          componentDidMount ( ) {       window .addEventListener ("mousemove" , this .handleMouseMove );     }     componentWillUnmount ( ) {       window .removeEventListener ("mousemove" , this .handleMouseMove );     }          render ( ) {       return  <WrappedComponent  {...this.state } />  ;     }   }   return  Mouse ; } const  Position  = props => (  <p >      鼠标当前位置:( x: {props.x}, y: {props.y} )   </p >  ); const  Cat  = props => <img  src ={src}  alt ="cat"  />  ;const  MousePosition  = withMouse (Position );const  CatPosition  = withMouse (Cat );class  App  extends  React.Component  {  render ( ) {     <div >        {/* 6. 渲染增强后的高阶组件 */}       <MousePosition  />        <CatPosition  />      </div >  ;  } } 
 
displayName 
在浏览器的 React 开发者工具中,复用的组件名字显示都是一样的,所以要设置displayName,设置不一样的名字,便于调试(React Developer Tools) 
 
1 2 3 4 5 6 7 8 9 10 11 12 function  withMouse (WrappedComponent ) {  class  Mouse  extends  React.component  {        }      Mouse .displayName  = ` WithMouse${getDisplayName(WrappedComponent)}  ` ;   return  Mouse ; } function  getDisplayName (WrappedComponent ) {  return  WrappedComponent .displayName  || WrappedComponent .name  || "Component" ; } 
 
传递 props 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const  Position  = props => {     console .log (props);   return  (     <p >        鼠标当前位置:( x: {props.x}, y: {props.y} )     </p >    ); }; class  App  extends  React.Component  {     render ( ) {     <div >        <MousePosition  a ="1"  />      </div >  ;  } } 
 
在<Mosue />中是可以得到 props 的,但是就没再往下传了 
 
修改,在class Mouse,继续传下去
1 2 3 render ( ) {	return  <WrappedComponent  {...this.state } {...this.props } />  } 
 
React 原理 
组件更新机制:父组件更新,其下面的子组件都会更新,子组件的子组件也会更新
 
Hooks Hooks  是仅在 React 显然的时候可用的函数,都是以use开头的,比如useState
setState 说明 1️⃣ setState()数据更新是异步的
1 2 3 4 5 6 7 8 9 10 11 12 handleClick = () =>  {      this .setState ({     count : this .state .count  + 1 ,    });      this .setState ({     count : this .state .count  + 1 ,    });      console .log ("count: " , this .state .count ); }; 
 
setState()是可以调用多次的,因为是异步的原因,所以后面setState()的执行不要依赖前面执行的结果 
但是render()只会执行一次 
 
2️⃣ 推荐语法:setState( (state, props) => {} )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 handleClick = () =>  {      this .setState ((state, props ) =>  {     return  {       count : state.count  + 1 ,      };   });      this .setState ((state, props ) =>  {     console .log ("第二次调用" , state);     return  {       count : state.count  + 1 ,      };   });   console .log ("count: " , this .state .count ); }; 
 
3️⃣ setState()的第二个参数,是一个回调函数
如果希望更新后执行什么操作,就可以使用这个回调函数 
在 DOM 渲染后执行(和componentDidMount可以相互使用) 
 
1 2 3 4 5 6 7 8 9 10 11 12 handleClick = () =>  {   this .setState (     (state, props ) =>  {       return  { count : state.count  + 1  };     },          () =>  {       console .log ("状态更新完成!" , this .state .count );     }    );   console .log ("count: " , this .state .count );  }; 
 
JSX 语法转换过程 
JSX 仅仅是createElement()的语法糖(简化语法) 
JSX 语法被@babel/preset-react插件编译为createElement()方法 
 
1 2 graph LR A(JSX语法) --> B(createElement) --> C(React元素) 
 
React 元素:是一个对象,用来描述希望在屏幕上看到的内容 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const  element = {    <h1  className ="app" > Hello</h1 >  } const  element = React .createElement (	"h1" ,     { className : "app"  },     "hello"  ) const  element = {    type : "h1" ,     props : {         className : "app" ,         children : "hello"      } } 
 
在 ES6 中,class 实现的类就是 ES5 中构造函数和原形的语法糖,可以使用typeof来测一下
 
组件性能优化 减轻 state 
state 中只存放跟组件渲染相关的数据 
比如像定时器 id 这样的不用放在 state 中,直接放在 this 中(this.timeId = setTimeout()) 
 
避免不必要的渲染 1️⃣ 父组件更新子组件也会跟着更新,有时候子组件会跟着有些不必要的更新
使用钩子函数shouldComponentUpdate(nextProps, nextState),通过返回值决定该组件是否重新渲染。true 重新,false 不重新 
钩子函数触发时机:组件重新渲染前执行。shouldComponentUpdate -> render 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class  App  extends  React.Component  {  state = { count : 0  };   handleClick = () =>  {     this .setState (state  =>  {       return  {         count : state.count  + 1 ,       };     });   };   shouldComponentUpdate (nextProps, nextState ) {                    console .log (nextState);          console .log (this .state );          if  (nextState.count  === this .state .count ) {       return  false ;     }     return  true ;   }   render ( ) {     console .log ("组件更新了" );     return  (       <div >          <h1 > 计数器:{this.state.count}</h1 >          <button  onClick ={this.handleClick} > +1</button >        </div >      );   } } 
 
纯组件 
PureComponent,其内部自动实现了shouldComponentUpdate钩子,不需要进行手动比较 
组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染 
 
1 2 3 4 class  App  extends  React.PureComponent  {   } 
 
纯组件内部的实现方式:shallow conpare浅层对比。
对于值类型,直接复制,对于引用类型:
1 2 3 4 5 const  obj = { number : 0  };const  newObj = obj;newObj.number  = 2 ; console .log (newObj === obj); 
 
接着上面的引用类型,若果在 React 中
1 2 3 4 5 state = { obj : { number : 0  } }; state.obj .number  = 2 ; setState ({ obj : state.obj  });
 
❗ 所以:state或props中属性的值为引用类型时,应该创建新的数据,不要直接修改原数据  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 const  newObj = { ...this .state .obj , number : Math .floor (Math .random () * 3 ) };this .setState (() =>  {  return  { obj : newObj }; }); this .setState ({  list : [...this .state .list , { 新数据 }], }); 
 
虚拟 DOM 和 Diff 算法 
只要state变化就重新渲染视图,有时候会浪费性能。解决这个问题,用到虚拟 DOM 和 Diff 算法 
虚拟 DOM 本质上就是一个 JavaScript 对象,用来描述希望看到的内容。(实际上就是 jsx 对象) 
 
执行过程 
初次渲染时,React 会根据初始 State。创建一个虚拟 DOM 对象(树) 
根据虚拟 DOM 生成真正的 DOM 然后渲染到页面中 
当数据变化后(setState()),重新根据新的数据,创建新的虚拟 DOM 对象 
使用diff算法,找到与上一个虚拟 DOM 对比,然后渲染需要更新的内容 
然后 React 只更新(patch)变化的内容,渲染到页面中 
 
render 方法的调用并不意味着浏览器中的重新更新,仅仅说明要进行 diff
虚拟 DOM 不是真正的 DOM,只要可以运行 JavaScript 的地方就可以使用,这就使得 React 可以脱离浏览器而存在,可以在 Android 和 IOS 中使用
 
Hooks Context Hooks 
远距离传输数据,不局限于父子组件,不使用 props 
 
Ref Hooks 
保存一些在渲染中不会用到的数据,比如 DOM 节点和计时器的 ID 
更新 ref 不会重新渲染组件 
一般会用到非 React 体系中 
 
Effect Hooks 
路由基础 SPA:单页应用程序,就是只有一个 HTML 页面的应用程序。用户体验好,对服务器压力小。 路由:就是组件和 URL 的对应关系,让用户到一个视图到另外的视图中。
基本使用 安装
1 2 3 npm install react-router-dom #  或者 yarn add react-router-dom 
 
导入三个核心组件
1 import  { BrowserRouter  as  Router , Route , Link  } from  "react-router-dom" ;
 
除了BrowserRouter外还有HashRouter,替换掉就行,不过推荐使用前者(使用的是 HTML5 的history API) 
 
使用<Router>组件包裹整个应用,然后使用<Link to="/xxx">指定路由入口,使用Route组件指定路由出口
1 2 3 4 5 6 7 8 9 10 11 12 const  First  = ( ) => {  return  <div > 我是First</div >  ; }; const  App  = ( ) => {  <Router >      <div >        <h1 > 我是路由</h1 >        <Link  to ="/first" > 页面一</Link >        <Route  path ="/first"  component ={First} > </Route >      </div >    </Router >  ;}; 
 
<Link>最终编译成<a>,to 被编译成 href;可以通过location.pathname得到 to 
<Route>的位置在哪,就在哪个位置渲染 
 
编程式导航 
就是通过 JavaScript 代码来实现页面跳转 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class  Login  extends  React.Component  {  handleLogin = () =>  {               this .props .history .push ("/home" );   };   render ( ) {     return  (       <div >          <p > 登录页面</p >          <button  onClick ={handleLogin} > 登录</button >        </div >      );   } } const  Home  = props => {  const  handleBack  = ( ) => {          props.history .go (-1 );   };   return  (     <div >        <h2 > 我是后台首页</h2 >        <button  onClick ={handleLogin} > 返回登录页面</button >      </div >    ); }; const  App  = ( ) => (  <Router >      <div >        <h1 > 编程式导航</h1 >        <Link  to ="/login" > 去登录页面</Link >        <Route  path ="/login"  component ={Login} > </Route >        <Route  path ="/home"  component ={Home} > </Route >      </div >    </Router >  ); 
 
默认路由 :进入页面时就会匹配的路由,使用/,后面不加内容
1 <Route  path="/" , component={Home }></Route > 
 
匹配模式 模糊匹配模式 
默认情况下 React 使用模糊匹配模式 
模糊匹配规则:只要 pathname 以/开头就会匹配成功 
 
1 2 3 4 5 6 7 8 9 <Route >   <div >      <h1 > 默认路由</h1 >      <Link  to ="/login" > 登录页面</Link >      <Route  path ="/"  component ={Home} > </Route >      <Route  path ="/login"  component ={Login} > </Route >    </div >  </Route > 
 
不管<Link>中的 to 里面的内容是什么(to=”/a”,to=”/abc”),<Route path="/">都会被匹配到 
同样,to="/login/a/b"也能匹配到path="/first" 
 
精确模式 
1 <Route  exact path="/login"  component={Login }></Route > 
 
推荐使用精确模式
其他 一些网站 组件 
chakra UI 
Material UI 
一些库 immer :修改state 的好帮手,比如对于嵌套好深的对象
NOTE 
一般情况下,错误都可以在页面报错信息中找到