如何引入React
从CDN引入React
//引入React:
https://.../react.x.min.js
//引入ReaatDOM:https://.../react-dom.x.min.js
cjs和umd的区别
cjs全称是CommonJS,是Node.js支持的模块规范
umd是统一模块定义,兼容各种模块规范
理论上优先使用umd,同时支持Node.js和浏览器
最新的模块规范是使用import和export关键字
通过WebPack引入React
import ... from ...
import React from 'react'
import ReactDOM from 'react-dom'
//注意大小写,尽量保持一致
对比React元素和函数组件
对比
App1 = React.createElement('div',null,n)
App是一个React元素
App = ()=> React.createElement('div',null,n)
App2是一个React函数组件
//函数App2是延迟执行的代码,会在被调用的时候执行
React元素
- createElement的返回值element可以代表一个div
- 但element并不是真正的div(DOM对象)
- 所以我们一般称element为虚拟DOM对象
()=>React元素
- 返回element的函数,也可以代编一个div
- 这个函数可以多次执行,每次得到最新的虚拟div
- React会对比两个虚拟div,找出不同(找出不同的算法就叫做DOM Diff算法),局部更新试图
JSX
使用JSX
方法一:CDN
- 引入babel.min.js
- 把
<script>改成<script type="text/babel"> - babel会自动进行转译
方法二:webpack+babel-loader
方法三:使用create-react-app
yarn global add create-react-app
create-react-app react-demo-1
cd react-demo-1
注意事项
- 注意className
<div className="red">n</div>
被转译为
React.createElement('div',{className:'red'},"n")
- 插入变量
标签里面的所有JS代码都要用{}括起来;
如果需要变量n,就用{}把n包起来
如果需要对象,就要用{}把对象包起来,例如
{{name:'wan-ti'}} - 习惯在return后面加()
React的两种组件
函数组件
function Welcome(props){
return <h1>Hello,{props.name}</h1>
}
使用方法:<Welcome name="wan-ti" />
函数组件注意事项
类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
使用方法:<Welcome name="wan-ti">
React.createElement的逻辑
- 如果传入一个字符串 'div',则会创建一个div
- 如果传入一个函数,则会调用该函数,获取其返回值
- 如果传入一个类,则在类前面加一个new,获取一个组件对象,然后调用对象的render方法,获取其返回值。
类组件注意事项
以代码为例
两种编程模型
Vue的变成模型
一个对象,对应一个虚拟DOM。
当对象的属性改变时,把属性相关的DOM节点全部更新
Vue为了其他的考量,同样引入了虚拟DOM和DOM diff
React的编程模型
一个对象,对应一个虚拟DOM。
另一个对象,对应另一个虚拟DOM
对比两个虚拟DOM,找不同(DOM diff),最后局部更新DOM
事件绑定
类组件的事件绑定
//写法一
class Son extends React.Component{
addN = () => this.setState({n:this.state.n+1});
render(){
return <button onClick={this.addN}>n+1</button>
}
}
//写法二:
class Son extends React.Component{
addN = () => this.setState({n:this.state.n+1});
addN(){
this.setState({n:this.state.n+1})
}
}
写法一和写法二的addN的区别在:
写法一的addN在对象上,写法二的addN在原型上;
写法二的this可变成window,写法一不会;
写法一是对象本身的属性,意味着每个Son组件都有自己的addN,如果有两个Son,就有两个addN;写法二是对象的共有属性(也就是原型上的属性),也就是说所有的Son组件共用一个addN
Class组件详解
两种方式创建Class组件
//ES5的方式
import React from 'react'
const A =React.createClass({
render(){
return (
<div>hi</div>
)
}
})
export default A
//ES6方式
import React from 'react'
class V extends React.Component{
super(props);
}
render(){
return (
<div>hi</div>
)
}
}
export default B;
Props-外部数据
//初始化
class B extends React.Component {
constructor(props){
super(props);
}
//读取
render(){
return <div onClick={this.props.onClick}>
{this.props.name}
<div>
{this.props.children}
</div>
</div
}
}//通过this.props.xxx读取
Props的作用
接受外部数据
- 只能读不能写
- 外部数据由父组件传递
接受外部函数
- 恰当的时机,调用该函数
- 该函数一般是父组件的函数
State&setState
//初始化
class B extends React.Component {
constauctor(props) {
super(props);
this.state = {
user:{name:'wan-ti',age:18}
}
}
//读写
render(){
//读
this.state.xxx.yyy.xxx;
//写
this.setState(newState,fn);
//setState不会立刻改变this.state,会在当前代码运行完后,再去更新this.state,从而触发UI更新
this.setState((state,props) => newState,fn)
//如果这么写回更容易理解一点
}
}
生命周期
函数列表
constructor()-在这里初始化state
static getDerivedStateFromProps()
shouldComponentUpdate-return false阻止更新
render()-创建虚拟DOM
getSnaphshotBeforeUpdate()
componentDidMount()-组件已更新在页面
componentDidUpdate-组件已更新
componentWillUnmount()--组件要隔屁了
static getDerivedStateFromError()
componentDidCatch()
constructor
用途:
- 初始化props
- 初始化state,但此时不能调用setState
- 用来写bind this
constructor(){
//写法一:
...
this.onClick = this.onClick.bind(this)
//写法二:
onClick = () => {}
constructor(){
...
}
}
- 也可以不写
shouldComponentUpdate
用途
- 返回true表示不阻止UI更新
- 返回false表示阻止UI更新
示例代码:
render
用途
- 展示试图
return (<div>...</div>) - 只能有一个根元素
- 如果有两个根元素,需要用
<React.Fragment>包起来 <React.Fragment />可以缩写为<></>
小技巧
- render里面可以写if...else
- render里面可以写?:表达式
- render里不可以直接写for循环,需要借用数组
- render里可以写array.map用于循环
componentDidMount
用途:
- 在元素插入页面后执行代码,这些代码依赖DOM
- 可发起加载数据的AJAX请求
- 首次渲染回执行此钩子
componentDidUpdate()
用途:
- 视图更新后执行代码
- 发起AJAX请求,用于更新数据
- 首次渲染并不会执行这个钩子
- 在这里使用setState会引起无限循环,可放在if里
- 若shouldCom...Update返回false,不触发这个钩子
componenWillUnmount
用途:
- 组件将要被移出页面然后销毁的时候执行代码
- unmount过的组件不会再次mount
钩子的执行顺序
函数组件详解
创建方式
const Hello = (props) => {
return <div>{props.message}</div>
}
const Hello = props => <div>{props.message}</div>
function Hello(props){
return <div>{props.message}</div>
}
特点:
- 没有state
- 没收生命周期
useEffect
//模拟componentDidMount
useEffect(()=>{ console.log('第一次渲染') },[])
//模拟componentDidUpdate
useEffect(()=>{ console.log('任意属性变更')})
useEffect(()=>{ console.log('n变了 ')}, [n])
//模拟componentWillUnmount
useEffect(()=>{
console.log('第一次渲染 ')
return ()=>{
console.log('组件要嗝屁了')
}
})
useState
尝试实现React.useState
let _state;
function muUseState(initialValue){
_state = _state === undefined ? initialValue : _state;
function setState(newState) {
_state = newState;
render();
}
return [_state,setState]
}
const render = () => ReactDOM.render(<App />,rootElement);
多个useState
let _state = [];
let index = 0;
function myUseState(initialValue) {
const currentIndex = index;
index += 1;
_state[currentIndex] = _state[currentIndex] || initialValue;
const setState = newState = > {
_state[currentIndex] = newState;
render();
};
return [_state[currentIndex,setState]
}
const render = () => {
index = 0;
ReactDOM.render(<App />,rootElement);
}
App组件更新过程
总结
- 每个函数组件对应一个React节点*
- 每个节点保存着state和index
- useState会读取state[index]
- index由useState出现的顺序决定
- setState会修改state,并触发更新
- 每次重新渲染,组件函数就会执行
- 对应的所有state都会出现 "分身"
- 如果不希望分身,可以选择使用useRef/useContext
React节点应该是FiberNode,_state的真实名字为memorizedState,index的实现用到了链表
Reack Hooks
useState
使用状态
const [n,setN]=React.useState(0)
const [user,setUser] =React.useState({name:'W'})
注意:
- 不可局部更新
- 数据变化的前提是地址发生变化
useState接受函数
const [state,setState] = useState(() => {
return initialState
})
//该函数返回初始state,且只执行一次
setState接受函数
setN(i => i+1)
useReducer
- 创建初始值initialState
- 创建所有操作reducer(state,action)
- 传给u色Reducer,得到读和写API
- 调用写({type:'操作类型'})
如何代替Redux
1:将数据集中在一个store对象
2:将所有操作集中在reducer
3:创建一个Context
4:创建对数据的读写API
5:将第四步的内容放到第三步的Context
6:用Context.Provider将Context提供给所有组件
7:各个组件用useContext获取读写API
useEffect
副作用
- 会改变环境,例如修改document.title
用途:
- 作为componentDidMount使用,[]作第二个参数
- 作为componentDidUpdate使用。可指定依赖
- 作为componentWillUnmount使用,通过return
- 以上三种可同时存在
特点:
如果同时存在对各useEffect,会按照出现次序执行
useLayoutEffect
布局副作用:
- useEffect在浏览器渲染完成后执行
- useLayoutEffect在浏览器渲染前执行
特点:
- useLayoutEffect总是比useEffect先执行
- useLayoutEffect里的任务非常容易影响Layout
经验:
为了用户体验,优先使用useEffect
useMemo
特点:
- 第一个参数是()=>value
- 第二个参数是依赖[m,n]
- 只有当依赖变化时,才会计算出新的value
- 如果依赖不变,就会重用之前的value
注意:
如果value是个函数,需要写成useMemo(() => (x) => console.log(x))
这是一个返回函数的函数
useCallback
用法:
useCallback(x => log(x),[m])
等价于
useMemo(() => x => log(x),[m])
useRef
目的:
在组件不断render时保持不变
初始化:const count = useRef(0)
读取:count.current
forwardRef
useRef
- 可以用来引用DOM对象
- 可以用来引用普通对象