目录
非受控组件(数据存在自己身上)// Uncontrolled:// Use `inputRef.current.value` to read the current value of
创建虚拟DOM
const VDOM= (
html内容
)
ReactDom.render( VDOM, getElementById(' test '))
jsx
新的js扩展,创建虚拟dom的语法糖
- ()内写html
- {}内写可写js表达式
- 表达式:会产生一个值
- if,for不能用,改用filter,foreach,map
- 样式类名class-->className
- 内联样式,style={{key:value}}
- 虚拟DOM只有一个根标签
- 标签首字母小写
函数式组件(简单组件,无状态)
需求
- 组件首字母大写(区分函数和类)
返回值是虚拟DOM
function Demo(){
return html代码
}
ReactDOM.render(<Demo/>,doucument.getElementById('test'))
执行了ReactDOM.render
1、React解析组件标签,找到组件
2、发现是函数定义,调用函数,返回的虚拟DOM转换为真实DOM,随后呈现在页面中
类式组件(复杂组件,有状态)
需求
- 必须继承React.Component
- 有render函数
class hello extends React.Component{
render()
{
return <h1>hellow world</h1>
}
}
ReactDOM.render(<hello/>,document.getElementById('test'))
执行了ReactDOM.render
1、React解析组件标签,找到组件
2、发现是类定义,调用函数,new实例,通过该实例调用render方法
3、将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类
class People {
constructor( name , age ){
this.name = name
}
speak(){
console.log( '!' )
}
}
class Student extends Person {
constructor(name,age,grade){
super(name, age)
this.grade=grade
}
}
- 子类继承继承的是父类原型链上的东西
- 父类的方法都是在原型上
- 子类写了构造器,必须使用super,且在第一行
因为子类没有自己this指针,super相当于A.prototype.constructor.call(this, props)
super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
类包含
1、方法
2、属性
3、state
实例组件三大属性(props、state、refs)
state--状态
class Weather extends React.Componets{
//初始化状态
constructor(props){
super(props)
this.state={isHot:true}
}
//渲染到页面
render(){
rerurn <h1>{this.state.isHot ? '炎热' : '凉爽' }</h1>
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
setState
只有通过setState修改,才会重新调用render,state才能修改在页面上
更新是一种合并,同名修改,不同名不管
class Weather extends React.Componets{
//初始化状态
constructor(props){
super(props)
this.state={isHot:true}
this.changeWeather=this.changeWeather.bind(this)
}
//渲染到页面
render(){
rerurn <h1 onClick={demo}>{this.state.isHot ? '炎热' : '凉爽' }</h1>
}
changeWeather(){
const isHot=!isHot
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
state、事件绑定简写
class Weather extends React.Componets{
//直接写赋值语句就是在类上添加属性。
a=1
//由此可以简写
state ={isHot :false,wind:'微风'}
changeWeather=()=>{
this.setState({isHot:!isHot})
}
render(){
rerurn <h1 onClick={demo}>{this.state.isHot ? '炎热' : '凉爽' }</h1>
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
自定义方法用赋值语句+箭头函数(箭头函数保证this指向外层实例)
Props
向组件传参数(只读)
class Person extends React.Component {
render(){
return (
<ul>
<li>姓名:{this.props.name}</li>
</ul>
)
}
}
ReactDom.render(</Person name='tom'>,,document.getElementById('test'))
向props中加入{ name: 'tom' }
批量传参
const p={name:'a',age:'12'}
ReactDOM.render(<Person { ...p } />).getElementById
传参限制
- ' ' : 传入的是字符串
- {} : 传入的是数字
ReactDOM.render(<Person name='marry' age={19} />).getElementById
参数限制
//属性类型和必要性的限制
Person.propTypes={
name:PropTypes.string.isRequired //传递的是字符串且必须
}
//属性默认值的限制
Person.defaultProps={
sex:'男' //默认值为男
}
Props简写
class Person extends React.Component {
//属性类型和必要性的限制
static propTypes={
name:PropTypes.string.isRequired //传递的是字符串且必须
}
//属性默认值的限制
static defaultProps={
sex:'男' //默认值为男
}
render(){
return (
<ul>
<li>姓名:{this.props.name}</li>
</ul>
)
}
}
ReactDom.render(</Person name='tom'>,,document.getElementById('test'))
函数组件的props
function Person (props){
return (
<ul>
<li>姓名:{props.name}</li>
</ul>
)
}
}
ReactDom.render(</Person name='tom'>,,document.getElementById('test'))
refs与事件处理
字符串形式的ref(老得)
refs:标识元素
class Demo extends React.Component{
showData=()=>{
console.log(this.refs.input)
}
render(){
return(
<div>
<input ref ='input' />
<button ref='button' onClick={this.showData}><button/>
</div>
)
}
}
ref值放入refs{ }中,可直接从refs 中读取
回调模式的ref
class Demo extends React.Component{
showData=()=>{
const {input1} =this
alert(input.value)
}
render(){
return(
<div>
<input ref ={c => this.input1 = c } />
<button ref='button' onClick={this.showData}><button/>
</div>
)
}
}
回调函数将节点加入到this上,从this上获取节点
createRef(只存储一个)
class Demo extends React.Component{
//调用后可以返回一个容器,该容器可以存储被Ref所标识的节点
myRef =React.createRef()
showData=()={
console.log(this.myRef)
}
render(){
return {
<div>
<input ref={this.myRef} /}
</div>
}
}
}
- 创建容器
- 在节点处,将其放入容器
- 使用时引用
事件绑定
事件绑定的this问题(简写在上)
class Weather extends React.Componets{
//初始化状态
constructor(props){
super(props)
this.state={isHot:true}
this.changeWeather=this.changeWeather.bind(this)
}
//渲染到页面
render(){
rerurn <h1 onClick={demo}>{this.state.isHot ? '炎热' : '凉爽' }</h1>
}
changeWeather(){
console.log(this)
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
类的局部函数都开起了严格模式,this指针直接调用时为undefined.
若直接调用changeWeather则此时this为undefined,需要在构造器中指定this指向。
自定义方法用赋值语句+箭头函数(箭头函数保证this指向外层实例)
事件处理
1、通过onXxx来指定事件处理函数
2、通过event.target得到发生事件的DOM元素对象
非受控组件(数据存在自己身上)
// Uncontrolled:
<input type="text" defaultValue="foo" ref={inputRef} />
// Use `inputRef.current.value` to read the current value of <input>
现用现取
受控组建(双向数据绑定,数据绑定state)
// Controlled:
<input type="text" value={value} onChange={handleChange} />
随数据改变,维护状态改变
高阶函数与函数柯里化
高级函数:满足两者之一
1、接受参数为函数
2、返回值是函数
科里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码方式
class login extends React.Component{
state={
username:'',
password:''
}
saveFormData =(dataType)=>{
return (event)=>{
this.setState( {[dataType]:event.target.value } )
}
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} >
</form>
)
}
}
旧生命周期
挂载
- constructor:
- 构造器,万物之初
- componentWillMount:
- 组件将要挂载
- render:
- 渲染
- componentDidMount:
- 组件挂载完毕
- componentWillUnmoount:
- 组件将要被卸载
更新
- componentWillRecieveProps:
- 将要接受新参数时触发(第一次传的参数不算)
- shouldComponentUpdate:
- 调用setState()时触发,返回值为真,则继续执行。返回为假,不再往后执行。不写默认为真是一个阀门
- componentWillUpdate:
- 组件将要更新
- forceUpdate()强制更新,调用时触发此生命周期函数
- render:
- 渲染
- componentDidUpdate:
- 将要更新
- componentWillUnmount:
- 将要卸载
新生命周期
去除
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
添加
- getDerivedStateFromProps(props,state)
- 只能是静态方法
- 返回值为新的状态对象或null
- state的值不能再改变了,只能取决与props
- getSnapshotBeforeUpdate(preProps,preState)
- 更新之前获取快照,使得组件在更新之前获取一些数据
- 返回值为(快照值)或null
- 快照值,更新前的数据,传给componentDidUpdate
- componentDidUpdate(preProps,preState,snapshot)
Diffing算法
key是虚拟dom的标识。在数据变化时,react会根据新数据生成新的虚拟dom,之后react进行虚拟dom,与旧虚拟 don的diff比较,规则如下
- 旧虚拟dom找到了与新虚拟dom相同的key:
- 若虚拟dom中内容没变,直接复用
- 若虚拟dom中的内容变化,生成新的真实dom,随后替换掉页面中之前的真实dom
- 旧虚拟dom中未找到与新虚拟dom相同的key
- 根据数据创建新的真实dom,随后渲染到页面
脚手架
public——静态资源
index.html:页面文件,用于被添加的节点
src——文件
index.js:入口文件。1、放置头文件资源。2、html渲染到public节点
App.js:组件外壳,放置组件
components——放置组件文件
react ajax
配置单个代理
package.jsion中的
proxy选项用于配置代理
配置多个代理
src中创建
setupProxy.js
兄弟组件间交互——消息订阅与发布机制
发送方:发布消息
PubSub.publish = function ( 'atguigu' , { name :LiHua , age : 18} )
接收方:接受消息
PubSub.subscribe( 'atguigu' , (data)=>{ handle(data) } )
路由
内置组件
- <BrowserRouter>
- <HashRouter>
- <Route>
- 注册路由
- 用Router包裹
-
<BrowserRouter> <Route path="path" component={component}/> </BrowserRouter>
- <Redirect>
- 都没匹配上之后,启动redirect(兜底方案)
-
<Switch> <Route path="" component={app}> <Redirect to = " path "> </Switch>
- <Link>
-
<BrowserRouter> <Link className='' to='Path'>点我变化</Link> </BrowserRouter>
- 必须用Router包裹(BrowserRouter/ HashRouter)
- 路由跳转链接
-
- <NavLink>
- 点谁就给谁多加一个样式名
-
<NavLink activeName='激活的样式名' to="path"></NavLink>
- <Switch>
- 匹配上之后不会继续匹配,避免多重匹配效率低下
-
<Switch> <Route path=/about' component={About}> <Route path=/about' component={Text}> </Switch>
其他
- history对象
- match对象
- withRouter函数
- 一般组件没有this.history,让一般组件使用history
- withRouter(component),之后component可以使用路由组件的api
路由组件与一般组件
- 写法不同
- 一般组件:<Demo/>
- 路由组件:
- <Link to="path">
- <Router path="path" component={Demo}>
- 存放位置不同
- 一般组件:component
- 路由组件:page
- 接受的props不同
- 一般组件:接受传递的参数
- 路由组件:接收到三个固定的属性
- history
- location
- match
路由的精准匹配与模糊匹配
模糊匹配:path = ”home/a/b“与path = “ home "可以匹配
严格匹配:<Route exact path="/about" component={ home }> 开启
嵌套路由
此时注册路由需要将前几级全部写上,保证最初路由能够匹配上
<MyNavLink to = "/home/news"> </MyNavLink>
<Route path="/home/news" component={Message}/>
路由传参
- params参数
- 路由链接(携带参数):<Link to=' /demo/text/tom/18 ' >详情</Link>
- 注册路由(声明接受):<Route path= " /demo/test/: name/:age" component={Test}/>
- 接收参数:this.props.match.params
- search参数
- 路由链接(携带参数):<Link to=' /demo/text?name=tom&age=18 ' >详情</Link>
- 注册路由(声明接受):<Route path= " /demo/test" component={Test}/>
- 接收参数:this.props.location.search
- state参数
- 路由链接(携带参数):<Link to=' /demo/text' , state:{name=tom&age=18 }>详情</Link>
- 注册路由(声明接受):<Route path= " /demo/test" component={Test}/>
- 接收参数:this.props.location.state
路由跳转的两种模式
- push模式
- replace模式
- 开启replace模式
<Link replace to={{pathname = "path" , state:{a:a,b:b}}}>
编程式路由导航
this.props.history.replace("/home/detail")
this.props.history.push("/home/detail")
redux
集中管理组件中共享的状态
action:动作对象
type:动作类型
data:动作数据
使用步骤
- 去除组件自身状态
- src下建立
- redux
- store.js
- count_reducer.js
- actionCreactor.js
- redux
- actionCreactor.js
- 创建action对象
- 处理 异步请求
- store.js:
- 引入redux中的createStore函数,创建一个store
- createStore调用时要传入一个为其服务的reducer
- conut_reducer:
- 本质是一个函数,接收两个参数(preState(之前状态),action(动作)),返回加工后的状态
- reducer有两个作用,初始化状态,加工状态
- reducer第一次调用时,是store自动触发的,传递的preState为undefined
- 在index.js中检测store状态的改变,一旦改变,重新渲染<APP/>
React——Redux
使用步骤
- 两个组件
- UI组件:不使用任何redux的api,只负责页面的呈现交互
- 容器组件:负责和redux通信,将结果交给UI组件
- 创建容器组件——connect函数
- connect(mapStateToProps,mapDispatchToProps)(UI组件)
- mapStateToProps:返回值是一个对象,为redux状态
- mapDispatchToProps:返回值是一个对象,为redux方法
- connect(mapStateToProps,mapDispatchToProps)(UI组件)
- 容器组件的store靠props传入,而非直接引入
- 可以不用自己检测状态改变
纯函数
Def:同样的输入,必定得到同样的输出(一样的参数,对应一样的返回)
遵守以下约束
- 不得改写参数数据
- 不会产生任何副作用
- 不能调用Date.now() Math.random() 等不纯的方法
redux的reducer必须是一个纯函数
setState更新状态两种写法
1、对象式
this.setState(stateChange, callback)
状态更新是异步的
stateChange:状态改变对象
callback:状态更新后调用的函数
2、函数式
this.setState(updater , callback)
updater:是函数,返回状态改变对象
updater(state,props)
lazyLoad
路由懒加载
引入部分
const Home = lazy( () => import ( "path" ) )
路由注册部分(需要一个兜底方案)
<Suspense fallback=> { 页面 } >
<Router path=" path " component={Home}>
</Suspense>
Hooks
在函数式组件中使用state以及其他的react特性
State Hook
- state hook 让函数组件也可以有state状态
- 语法:const [ xxx , setxxx ] = React.useState( initValue )
- useState( )说明:
- 参数:第一次初始化制定的值在内部作缓存
- 返回值:包含两个元素的数组,第一个为内部当前状态值,第二个为更新状态值的函数
- setxxx()两种写法
- setXxx(newValue):参数为非函数值,直接指定新的状态值,覆盖原来的值
- setXxx(value => newValue ):参数为函数,接收原来的值,返回新的状态值
Effect Hooks
在函数式组件中使用生命周期
- 语法:
React.useEffect( () => {
//DidMount 和 DidUpdata
return () => {
//willUnmount
}
} , [stateValue] ) //若指定[] 回调函数只会在第一次render后执行,其他情况则为监视[]值
Ref Hooks
在函数组件中引用元素
语法:const refContainer = useRef()
Context
用于组件间通信,用于祖宗组件与后代组件之间通信
步骤
- 创建Context容器对象
- const XxxContext = React.createContext( )
- 渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代传递数据
<xxxContext.Provider value = {数据 }> 子组件 </xxxContext.Provider>
-
后代组件读取数据
//适用于类组件 static contextType = xxxContext //声明接收context this.context //读取context中的数据 //函数组件与类组件都可以 <xxxContext.Consumer> { value => (value.xxx) } </xxxContext.Consumer>
Purecomponent
当props、state不改变时,调用setState会重新刷新,效率低下。应该在shouldComponentUpdate(),中进行判断,只有当状态不同时才重新渲染。
pureComponent将这一部分逻辑封装,可以直接用pureComponent
export default class Parent extends PureComponent{ }
其中有一个问题,在比较时只浅比较,引用对象只比较地址。在对比引用对象时,应该避免与原对象相关。
renderProps(类似插槽)
有两种方法形成父子关系
1、父亲在jsx中调用子组件
2、在祖宗中 (想在编码时才确定父子关系时,可以这样调用)
<A>
<B/>
</A>
此时可以通过this.props.chidren调用
通信方式总结
- props
- 消息订阅与发布
- 集中式管理(redux)
- conText:生产者-消费者模式
父子组件:props
兄弟组件:消息订阅与发布、集中式管理
祖孙组件:消息订阅-发布、集中式管理、conText