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即可
- 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是只读属性,不可修改,可以是:
-
JSX 标签信息,如
className、src、alt、width和height等 -
对象或其他任意类型的值,如:父组件中的 state,或者是callback
-
父子组件通信
父--->子:方法/标签,子通过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>;
};
//子组件中传入一个回调函数,获取到子组件包装里面的值,这样子组件可以专注业务逻辑,父组件作为展示
- 兄弟组件通信
状态提升机制,通过父组件进行兄弟之间的数据传递:
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>
);
}
- 多层级间组件通信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() 的工作,将新元素添加到数组的末尾和开头。
通常有两种更改数据的方法。第一种方法是通过直接更改数据的值来改变数据。第二种方法是使用具有所需变化的新副本替换数据。好处。
回顾游戏的历史可以用上,撤消和重做某些操作的能力是应用程序的常见要求,
避免所有子组件(未受变化影响的子组件)都会自动重新渲染。
- useEffect
可以把useEffect Hook看作三个函数的结合:
•componentDidMount
•componentDidUpdate
•componentWillUnmount
3.useRef
生成一个容器,可以获取到元素,refHook可以让函数式组件实现类似ref的特性。
虚拟Dom
真实的DOM上,默认会挂载很多属性和方法,而 virtual Dom只有部分属性方法,是一种对象结构(type:实际的标签;props:标签内部的属性(除key和ref,会形成单独的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渲染的要慢。缩短了路程时间,但是交通工具还是两只脚。
- 加强的兼容性,可分为:浏览器的兼容和跨平台兼容
•React基于虚拟DOM实现了一套自己的事件机制,并且模拟了事件冒泡和捕获的过程,采取事件代理、批量更新等方法,从而磨平了各个浏览器的事件兼容性问题,开发只需操作统一的virtual Dom,而不用写许多兼容代码。从底层的 DOM 操作和浏览器差异性中解放出来。
•对于跨平台,React和React 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属性)属性时会报错,后端返回的JSON不支持Symbol类型,Symbol类型的值丢失。即使服务端存在用JSON作为文本返回安全漏洞,JSON里也不包含 Symbol.for('react.element'),React也不会渲染处理,从而达到预防XSS的功能
diff
- tree层级
DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较,只有删除、创建操作,没有移动操作
- conponent层级。
如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的。
- 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