react新手入门知识点

27 阅读10分钟

jsx语法规则:

1.标签中混入 js表达式时要用 {}, 使用内联样式时,要用双括号写法(使用小驼峰写法写css样式)

2.可以通过使用 JavaScript 的 if 语句、&&? : 运算符来选择性地渲染 JSX。

3.标签中类名指定不用 class,要用 className

4.添加事件属性时(如onclick),on后面的单词首字母要大写(如onClick

5.虚拟DOM必须只有一个根标签,

6.组件开头需大写。因为浏览器不认识jsx语法的,要通过Babel去对JSX进行转化为对应的JS对象才能让浏览器识别,此时就会有个依据去判断是原生DOM标签,还是React组件,而这个依据就是标签的首字母

◦标签开头为小写时,会被 jsx编译为 html标签,若 html没有对应同名元素则报错

◦标签开头为大写时,会被 jsx识别为组件,若没找到对应组件则

7.组件中引入 .module.css 文件

css文件作为一个模块引入,这个模块中的所有css,只作用于当前组件。不会影响当前组件的后代组件.

import'./App.module.css';

这种方式是webpack特工的方案,只需要配置webpack配置文件中modules:true即可

  1. render的原理

render中编写的jsx,通过babel编译后就会转化成我们熟悉的js格式,比如

return(
  <div className='cn'>
    <div> start </div>
  </div>
)

babel编译后:

return(
  React.createElement(//创建Dom节点,构成的虚拟Dom tree最终转化成真实Dom
    'div',//type:标签
    {
      className :'cn'
    },//attributes:标签属性,若无则为null
    React.createElement(
      'div',
      null,
      'start'
    ),//children:标签的子节点
  ))

执行render函数时,如果不是第一次创建Dom,React将新调用的返回的virtue Dom Tree与旧版本的 tree进行比较,就是所谓的diff比较,然后更新DOM树。

触发时间:函数组件通过useState-- setSate这种形式更新数据时,不过当数组的值改变前后相同,就不会触发render。父组件或者祖先组件的state发生了改变,就会导致父组件的重新渲染---子组件跟着重新渲染。

优化:多余的渲染是消耗性能,useMemo用来缓存组件的渲染,避免不必要的更新。昂贵的判断:创建或循环数千个对象,time>1ms,

  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),//执行的昂贵计算
    [todos, tab]                  //依赖项
  );
在严格模式下,React 将调用visibleTodos两次,React 会使用其中一个结果,并忽略另一个。所以组件和计算函数应该是纯粹的

组件的通信

props是只读属性,不可修改,可以是:

  1. JSX 标签信息,如classNamesrcaltwidthheight

  2. 对象或其他任意类型的值,如:父组件中的 state,或者是callback

  3. 父子组件通信

父--->子:方法/标签,子通过props.children拿到,子--->父:callback,父出个方法,子那边执行callback

App.js 
<Child getSonMsg={getSonMsg}>
    <h2>this is Child组件里面的h2</h2>
  </Child>

Child.js
 function Child(props) {
    const sonMsg='i am Son Msg';
    return <div> props过来的h2 {props.children} 
    <button onClick={()=>props.getSonMsg(sonMsg)}>son creat mag</button></div>;
  };
//子组件中传入一个回调函数,获取到子组件包装里面的值,这样子组件可以专注业务逻辑,父组件作为展示
  1. 兄弟组件通信

状态提升机制,通过父组件进行兄弟之间的数据传递:

function A({ getAName }) {
  const aName = "A";
  return (
    <div>
      this is A<button onClick={() => getAName(aName)}>send</button>
    </div>
  );
}

function B(props) {
  return <div>this is B;{props.name}</div>;
}

function App() {
  const [name, setName] = useState("");
  const getName = (names) => {
    console.log(names);
    setName(names);
  };
  return (
    <div className="App">
      Hello World!
      <A getAName={getName} />
      <B name={name} />
    </div>
  );
}

  1. 多层级间组件通信context 1、createContext方法创建一个上下文对象 2、在顶层组件通过Provider组件提供数据 3、在底层组件通过useContext钩子函数使用数据

例如:

import { createContext, useContext } from "react"

const MsgContext = createContext()

function A(){
  return(
    <div>
      this is A component
      <B/>
    </div>
  )
}
function B(){
  const msg = useContext(MsgContext)
  return (
    <div>
      this is B component,{msg}
    </div>
  )
}
function App(){
  const msg = 'this is app msg'
  return (
    <div>
      <MsgContext.Provider value={msg}>
        this is App
        <A/>
      </MsgContext.Provider>
    </div>
  )
}
export default App

使用频率高的hook

1.useState

[state,setState]=useState( ),setState()后页面会在下一次事件循环时渲染新值。

这是因为setState改变了状态并导致重新渲染,可能是一项昂贵的操作,异步的以及批处理(需要更新的state合并后放入状态对列,而不会立刻更新this.state,并且会合并,更新最后一个setState)以获得更好的UI体验和性能。

setState()后马上console.logstate的是旧值,,因为后面的代码使用的state的其实是state的“快照”,等下一次事件循环中页面渲染完成后再更新代码中的state。函数式写法不支持回调函数了。可以用useEffect。

在 state 中存放对象时,直接修改对象并不会触发重渲染,并会改变前一次渲染“快照”中 state 的值。所以不要直接修改一个对象,而要为它创建一个版本,并通过把 state 设置成这个新版本来触发重新渲染。

setArtists([
  {id:nextId++,name:name},
  ...artists// 组展开运算符还允许你把新添加的元素放在原始的 ...artists 之前/放在末尾数
]);

//删除
setArtists(
      artists.filter(a =>
      a.id !== artist.id
          )
//改
setArtists(
        shapes.map(shape => {
          ...shape,
          y: shape.y + 50,});
      );
//插入
const nextArtists = [
      // 插入点之前的元素:
      ...artists.slice(0, insertAt),
      // 新的元素:
      { id: nextId++, name: name },
      // 插入点之后的元素:
      ...artists.slice(insertAt)
    ];

这样一来,展开操作就可以完成 push()unshift() 的工作,将新元素添加到数组的末尾和开头。

通常有两种更改数据的方法。第一种方法是通过直接更改数据的值来改变数据。第二种方法是使用具有所需变化的新副本替换数据。好处。

回顾游戏的历史可以用上,撤消和重做某些操作的能力是应用程序的常见要求,

避免所有子组件(未受变化影响的子组件)都会自动重新渲染。

  1. useEffect

可以把useEffect Hook看作三个函数的结合:

componentDidMount

componentDidUpdate

componentWillUnmount

3.useRef

生成一个容器,可以获取到元素,refHook可以让函数式组件实现类似ref的特性。

虚拟Dom

真实的DOM上,默认会挂载很多属性和方法,而 virtual Dom只有部分属性方法,是一种对象结构(type:实际的标签;props:标签内部的属性(除keyref,会形成单独的key名);children: 为节点内容,依次循环),对真实dom的抽象,达到既轻量方便重建又还能操作真实Dom的目的即可。

那怎么操作真实Dom的呢?是将所有的操作聚集到一块,计算出所有的变化后,统一更新一次虚拟DOM。一个页面如果有500次变化,没有虚拟DOM的就会渲染500次,而采用虚拟DOM的方式,只需要渲染一次,从这点上来看,页面越复杂,虚拟DOM的优势越大。

优势:

1.提升性能

React会将整个DOM保存为虚拟DOM,如果Dom有更新,则会比较之前的状态当前的状态,并会确定哪些状态被修改,然后将这些变化更新到实际DOM上,更新局部的真实Dom。可能有人会问:“为什么不直接对比Dom”,因为有一个前提,浏览器在处理DOM的时候会很慢,处理JavaScript会很快。 同时,虚拟DOM会减少了非常多的DOM操作 ,所以性能会提升很多。

但是virtual Dom一定就提升性能了吗?非也,到头来还是要原生方法操作真实Dom,因为虚拟DOM虽然会减少DOM操作,但也无法避免DOM操作,它的优势是在于diff算法批量处理策略。在首次渲染上,虚拟DOM会多了一层计算,消耗一些性能,所以有可能会比html渲染的要慢。缩短了路程时间,但是交通工具还是两只脚。

  1. 加强的兼容性,可分为:浏览器的兼容跨平台兼容

React基于虚拟DOM实现了一套自己的事件机制,并且模拟了事件冒泡和捕获的过程,采取事件代理批量更新等方法,从而磨平了各个浏览器的事件兼容性问题,开发只需操作统一的virtual Dom,而不用写许多兼容代码。从底层的 DOM 操作和浏览器差异性中解放出来。

•对于跨平台,ReactReact Native都是根据虚拟DOM画出相应平台的UI层,只不过不同的平台画法不同而已

从jsx是怎么转变成virtual Dom的呢?

每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖,调用后转成ReactElement 对象(React.createElement函数将props和子元素进行处理后返回一个ReactElement对象(key和ref会特殊处理))这个对象包含的属性有

{
type:实际的标签
props:标签内部的属性(除key和ref,会形成单独的key名)
children: 为节点内容,依次循环

type:实际的标签,原生的标签(如'div'),自定义组件(类或是函数式)
props:标签内部的属性(除key和ref,会形成单独的key名)
key:组件内的唯一标识,用于Diff算法
ref:用于访问原生dom节点
owner:当前正在构建的Component所属的Component
?typeof:默认为REACT_ELEMENT_TYP,可以防止XXS(注入恶意指令代码到网页,获取到cookie storage)
}

说到攻击,这里拓展一下,react如何防范xss攻击

1.ReactDOM负责DOM层的所有事务,在渲染输入内容前会默认进行转义,转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。这样前端层的防范工作大致完成了。

2.?typeof:默认为REACT_ELEMENT_TYP

var REACT_ELEMENT_TYPE = (typeofSymbol === 'function' 
&& Symbol.for && Symbol.for('react.element')) || 0xeac7;

?typeof实际上是Symbol类型,那么这个变量为什么可以预防XSS呢?

简单的说,没有typeof(?typeof渲染成dom时是typeof(?typeof渲染成dom时是typeof属性)属性时会报错,后端返回的JSON不支持Symbol类型,Symbol类型的值丢失。即使服务端存在用JSON作为文本返回安全漏洞,JSON里也不包含 Symbol.for('react.element'),React也不会渲染处理,从而达到预防XSS的功能

diff

  1. tree层级

DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较,只有删除、创建操作,没有移动操作

  1. conponent层级。

如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的。

  1. element层级

对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识

提供了 3 种节点操作,分别为INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和REMOVE_NODE(删除)

通过key可以准确地发现新旧集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动。

对于简单列表渲染而言,不使用key比使用key的性能好,因为dom节点的移动操作开销是比较昂贵的,例如我们对这个集合[1,2,3,4]进行增删的操作改成[1,3,2,5]

1.加key
<div key='1'>1</div>             <div key='1'>1</div>     
<div key='2'>2</div>             <div key='3'>3</div>  
<div key='3'>3</div>  ========>  <div key='2'>2</div>  
<div key='4'>4</div>             <div key='5'>5</div>  
操作:节点2移动至下标为2的位置,新增节点5至下标为4的位置,删除节点4。

2.不加key
<div>1</div>             <div>1</div>     
<div>2</div>             <div>3</div>  
<div>3</div>  ========>  <div>2</div>  
<div>4</div>             <div>5</div>  
操作:修改第1个到第4个节点的innerText

参考链接:juejin.cn/post/711632…

参考juejin.cn/post/724440…

参考react.dev/reference/r…