react核心思想
- 数据驱动界面更新
- 组件化
虚拟DOM
基本使用
- react.js主要用于'生成虚拟DOM'
- react-dom.js主要用于'将虚拟DOM转换为真实DOM' React.createElement(元素名称,元素属性,元素内容)创建虚拟DOM
ReactDOM.render(渲染元素,挂载到的容器元素,回调函数)渲染虚拟DOM,多次渲染, 后渲染会覆盖先渲染,render方法一次只能渲染一个元素/组件
在react中事件名称采用驼峰的写法,React.createElement('button', {onClick: btnClick}, '按钮');
JSX
- 作用:JSX作用编写React中的页面结构体,
- 简介:JSX === JavaScript + X === (XML) === (eXtension),JSX 是一个看起来很像 XML 的 JavaScript 语法扩展
编写规范:
- 顶层只能有一个根元素,
- 标签可以是单标签也可以是双标签, 但如果是单标签必须闭合(/>)
- 建议使用()将JSX代码包裹起来
使用JSX有点:
- 使用JSX使得我们在React中编写页面结构更为简单、灵活
- JSX 是类型安全的,在编译过程中就能发现错误
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化
- 防止XSS注入攻击
绑定事件:事件名称必须采用驼峰写法
监听方法this处理:
- 箭头函数
- 创建时通过bind修改
- 注册时通过bind修改
- 普通函数和箭头函数结合
事件传参:通过箭头函数和普通函数结合方式可以获取传递的参数
<div>
<button onClick={(e)=>{this.btnClick(1,2,e)}}>按钮</button>
</div>
合成事件对象:合成事件是React在浏览器事件基础上做的一层包装,可通过nativeEvent属性获取浏览器原生事件
浏览器无法识别JSX,导入babel.js,在script标签上添加type="text/babel",借助babel将JSX转换成JS, 转换成React.createElement();
JSX注释编写:在JSX中遇到<会当做XML元素解析, 遇到{会当做JS解析,所以在JSX中不能使用HTML的注释,注释需要写在{}括号中
JSX绑定类名:class在js中是关键字,所以不能直接绑定属性,需要绑定在calssName属性上
JSX绑定动态类名:元素只有单个类名,可以通过三元运算符绑定,多个类名
JSX实现v-if功能和v-show功能:
<p style={{display: this.state.flag?'block':'none'}}>我是段落</p>
{this.state.flag && <p>动态显示</p>}
JSX实现v-for功能:
class App extends React.Component {
constructor(props) {
super(props);
}
state = {
list:[[1,2],[3,4,5],[6,7]]
}
render() {
return (
<div>
{
this.state.list.map((item,index)=>
<div key={index}>
{
item.map((subItem,subIndex)=>
<div key={subIndex}>{subItem}</div>
)
}
</div>
)
}
</div>
);
}
}
<div className={`container ${index===this.state.currentIndex?"active":null}`}></div>
JSX绑定样式:由于样式是键值对形式的, 所以在JSX中如果想要动态绑定样式,必须将样式放到一个对象中, 并且所有以-连接的样式名称都要转换成驼峰命名
<p style={{color:'red', fontSize:'50px'}}>绑定样式</p>
函数组件
通过ES6之前的构造函数来定义(无状态组件),在构造函数中返回组件的结构即可,函数组件也称为无状态组件,
function Home() {
return (
<div>
<div>{message}</div>
<button onClick={btnClick}>按钮</button>
</div>
);
}
类组件
定义一个类, 在这个类中实现render方法, 在render方法中返回组件的结构即可,有状态组件,组件中的状态(state)指的其实就是数据
继承于React.Component的组件, 默认都会从父类继承过来一个state属性
class Home extends React.Component{
render(){
return (
<div>
<div>{message}</div>
<button onClick={btnClick}>按钮</button>
</div>
)
}
}
监听方法this指向
React内部在调用监听方法的时候, 默认会通过apply方法将监听方法的this修改为了undefined
父子组件通信
设置默认值以及校验参数类型都是通过实例的静态属性设置,在构造函数中通过
构造函数.属性 = 属性值 方式设置
在类中通过static静态属性设置
函数式组件父传子:
父组件传递的所有数据放到pros对象中传递给子组件
<Son name={'lmh'} age={18}/>
子组件设置参数默认值
Son.defaultProps = {name:"lmh",age:"20"}
子组件校验参数类型,安装prop-types插件
npm install prop-types
Son.propTypes = {name:PropTypes.string,age:PropTypes.number}
类组件父传子:
class Main extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div className={'main'}></div>
)
}
设置参数默认值
static defaultProps = {
name: '知播渔',
age: 666
}
校验参数类型
static propTypes = {
name: ReactTypes.string,
age: ReactTypes.number
}
}
子传父
1.父组件传递一个方法给子组件
2.子组件在调用这个方法的时候, 就可以通过方法传参的形式给父组件传递数据
<Footer fatherFn={this.myFn.bind(this)}/>
跨组件通信父传子
通过context上下文传递, 1:调用React.createContext({})创建上下文,返回生产者组件Provider和消费者组件Consumer,
2:Provider生产者容器组件生产数据,包括需要消费生产组件数据的后代,value属性为生产组者组件生产的数据
3:Consumer消费者容器组件, 专门用于消费生产者容器组件生产的数据的
class App extends React.Component{
render(){
return (
<Provider value={{name:'lnj', age:18}}>
<Father></Father>
</Provider>
)
}
}
class Father extends React.Component{
render(){
return (
<Consumer>
{
(value)=>{
return (
<div>
<p>{value.name}</p>
<p>{value.age}</p>
</div>
)
}
}
</Consumer>
)
}
}
设置子组件的上下文contextType,消费数据
const AppContext = React.createContext({
name:'知播渔',
age: 666
});
Son.contextType = AppContext;
class Son extends React.Component{
render(){
return (
<div>
{/*3.从当前组件的上下文中消费数据*/}
<p>{this.context.name}</p>
<p>{this.context.age}</p>
</div>
)
}
}
跨组件通信子传父,兄弟组件通信
使用event库跨组件通信,npm install events
event库常用api:
- 监听事件:eventBus.addListener("事件名称", 监听函数)
- 移除事件:eventBus.removeListener("事件名称", 监听函数)
- 触发事件:eventBus.emit("事件名称", 参数列表)
全局创建 const eventBus = new EventEmitter();
class A extends React.Component {
render(){
return (
<div>
<button onClick={this.btnClick.bind(this)}>发送数据给B</button>
</div>
)
}
btnClick(){
eventBus.emit('getData','lmh',20)
}
}
class B extends React.Component {
constructor(props){
super(props)
this.state = {
name:'',
age:'',
}
}
componentDidMount(){
eventBus.addListener('getData',this.getData.bind(this))
}
componentWillUnmount(){
eventBus.removeListener('getData',this.getData)
}
getData(name,age){
this.setState({
name:name,
age:age
})
}
render(){
return (
<div>
<span>年龄:{this.state.age}</span>
<span>姓名:{this.state.name}</span>
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div>
<A></A>
<B></B>
</div>
)
}
}
props和state区别
props用于接收父组件传递的数据,只读
state是组件自己的数据,可修改,
setState是同步的还是异步的
1.setState是同步的还是异步的?
默认情况下setState是异步的
2.为什么setState是异步的?
主要是为了优化性能
3.如何拿到更新之后的数据?
setState方法其实可以接收两个参数,通过setState方法的第二个参数, 回调函数拿到
this.setState({
age: 111
}, ()=>{
console.log('回到函数中', this.state.age);
});
4.setState一定是异步的吗?
不一定: 在定时器中, 在原生事件中
componentDidMount() {
const oBtn = document.getElementById('btn');
oBtn.onclick = () => {
this.setState({
age: 666
});
console.log(this.state.age); // 666
}
}
setTimeout(()=>{
this.setState({
age: 666
});
console.log(this.state.age); // 666
}, 0);
5.setState是如何给state赋值的
通过Object.assign(),let obj = Object.assign({}, oldObj, newObj)
6:State合并现象
setState会收集一段时间内所有的修改再更新界面,所以就出现了State合并现象
btnClick(){
/*
1.为什么最终的一个值是1, 不是3
因为setState默认是一个异步的方法, 默认会收集一段时间内所有的更新, 然后再统一更新
所以就导致了最终的一个值是1, 不是3
* */
this.setState({
age: this.state.age + 1
});
// console.log(this.state.age); // 0
this.setState({
age: this.state.age + 1
});
this.setState({
age: this.state.age + 1
});
}
let oldObj = {age: 0};
let stateList = [
// {age: oldObj.age + 1},
// {age: oldObj.age + 1},
// {age: oldObj.age + 1},
// {age: 0 + 1},
// {age: 0 + 1},
// {age: 0 + 1},
{age: 1},
{age: 1},
{age: 1}
];
stateList.forEach((newObj)=>{
// Object.assign({}, {age: 0}, {age: 1}); // {age: 1}
// Object.assign({}, {age: 1}, {age: 1}); // {age: 1}
// Object.assign({}, {age: 1}, {age: 1}); // {age: 1}
oldObj = Object.assign({}, oldObj, newObj);
});
7:解决State合并现象 setState传入函数,函数返回对象形式,函数接收state,props
btnClick(){
this.setState((preState, props)=>{
return {age: preState.age + 1}
})
this.setState((preState, props)=>{
return {age: preState.age + 1}
})
this.setState((preState, props)=>{
return {age: preState.age + 1}
})
}
生命周期方法
1.React组件生命周期方法
组件从创建到销毁的过程, 在特定的时间节点调用的方法
- constructor函数:组件被创建的时候, 就会调用,通过props接收父组件传递的数据,this.state初始化内部的数据,bind为事件绑定实例(this)
- render函数:渲染组件的时候, 就会调用,返回组件的网页结构
- componentDidMount函数:组件已经挂载到DOM上时,操作DOM,发送网络请求,添加订阅
- componentDidUpdate函数:组件已经发生了更新时
- componentWillUnmount函数:组件即将被移除时,取消网络请求,清除定时器
- shouldComponentUpdate函数:组件数据变更,决定是否更新组件,可以获取到组件更新后的数据,返回true则更新UI视图
- static getDerivedStateFromProps函数:挂载或更新,映射数据到state中,
- getSnapshotBeforeUpdate函数:更新时最后能获取更新前数据
组件创建时执行的生命周期方法: constructor(构造器)=>static getDerivedStateFromProps(映射数据到state中)=>render(生成结构)=>componentDidMount(挂载组件)
组件更新时执行的生命周期方法:static getDerivedStateFromProps(映射数据到state中)=>shouldComponentUpdate(决定是否更新UI)=>render(重新渲染UI)=>getSnapshotBeforeUpdate(最后可以获取更新前数据)=>componentDidUpdate(更新完成)
react渲染流程
执行render方法=>将JSX转换成createElement=>执行createElement创建虚拟DOM, 得到虚拟DOM树=>根据虚拟DOM树在界面上生成真实DOM
React更新流程
props/state发生改变=>render方法重新执行=>将JSX转换成createElement=>利用createElement重新生成新的虚拟DOM树=>新旧虚拟DOM通过'diff算法'进行比较=>每发现一个不同就生成一个mutation=>根据mutation更新真实DOM
React-Diff算法
- 只会比较同层元素=>根元素类型不同则直接生成新的结构
- 只会比较同位置元素(默认)=>给元素添加key,则同层元素都会比较
- 在比较过程中: 同类型元素做修改,不同类型元素重新创建
组件性能优化
父组件render被调用, 那么所有后代组件的render也会被调用, 类组件:创建组件时让组件继承于React.PureComponent 函数组件:通过高阶函数React.memo(),返回优化后的组件
const PurHome = React.memo(function() {
return (
<div>
<p>Home</p>
</div>
)
});
获取元素
通过createRef()创建一个对象, 然后将这个对象传递给ref
constructor(props){
super(props);
this.oPRef = React.createRef();
}
<p ref={this.oPRef}>我是段落</p>
this.oPRef.current属性获取元素
通过传递一个回调函数, 然后保存回调函数参数的方式
constructor(props){
super(props);
this.oPRef = null;
}
<p ref={(arg)=>{this.oPRef = arg}}>我是段落</p>
注意点:
- 获取原生元素, 拿到的是元素本身
- 获取类组件元素, 拿到的是组件实例对象,获取函数组件元素, 拿不到任何内容
Ref转发:获取函数式组件内部元素
const About = React.forwardRef(function(props, myRef) { // myRef === this.myRef
return (
<div>
<span ref={myRef}>我是span</span>
</div>
)
});
class App extends React.PureComponent{
constructor(props){
super(props);
this.myRef = React.createRef();
}
render(){
return (
<div>
<About ref={this.myRef}/>
</div>
)
}
}
受控组件
表单元素内部形成一个闭环,值由内部state维护控制
React传送门
默认情况下, 所有的组件都是渲染到root元素中的,Portal提供了一种将组件渲染到其它元素中的能力
class Modal extends React.PureComponent{
render() {
/*
this.props.children: 可以获取到当前组件所有的子元素或者子组件
createPortal: 接收两个参数
第一个参数: 需要渲染的内容
第二个参数: 渲染到什么地方
* */
console.log(this.props);
return ReactDOM.createPortal(this.props.children, document.getElementById('other'));
}
}
class App extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return (
<div id={'app'}>
<Modal>
<div>123123</div>
<div>123123123</div>
</Modal>
</div>
)
}
}
空元素
React.Fragment
React.StrictMode
开启严格模式, 检查后代组件中是否存在潜在问题
- 和Fragment一样, 不会渲染出任何UI元素
- 仅在'开发模式'下有效
React样式
-
内联样式优点:可以获取state中的状态,缺点需要使用驼峰,样式无提示 -
外联样式优点:编写简单, 有代码提示, 支持所有CSS语法,缺点:不可以动态获取当前state中的状态,默认属于css全局样式会相互影响 -
CSS模块化样式文件命名:样式文件名.module.后缀名 优点:解决全局样式污染,缺点:不可以动态获取state
css-in-js
简介:用JS来编写CSS的库
styled-components使用
安装styled-components,npm install styled-components --save
导入styled-components,import styled from 'styled-components';
创建 StyleDiv组件并且设置子元素样式
const StyleDiv = styled.div`
p{
font-size: 50px;
color: red;
}
a{
font-size: 25px;
color: green;
}
`;
styled-components特性
1:props
const StyleDiv = styled.div`
p{
font-size: 50px;
color: ${props => props.color};
}
a{
font-size: 25px;
color: green;
}
`;
render() {
return (
<StyleDiv color={this.state.color}>
<StyleInput></StyleInput>
</StyleDiv>
)
}
2:attrs
调用完attrs方法之后, 这个方法返回的还是一个函数
// 所以我们还可以继续通过字符串模板来调用
const StyleInput = styled.input.attrs({
type:'password'
})``
3:设置主题
import {ThemeProvider} from 'styled-components'
<ThemeProvider theme={{size:'50px', color:'blue'}}></ThemeProvider>
4:继承
const BaseDiv = styled.div`
font-size: 100px;
background: blue;
`;
const StyleDiv1 = styled(BaseDiv)`
color: red;
`;
const StyleDiv2 = styled(BaseDiv)`
color: green;
`