React基础
REACT(面向组件开发) 1.react高效原因 1)使用虚拟DOM技术,不总是直接操作页面真实DOM 2)DOM Diffing算法,最小化界面重绘 2.react使用jsx语法 js创建虚拟DOM太繁琐,所以使用jsx创建虚拟DOM 3.关于虚拟DOM 1.本质是Object类型的对象 2.虚拟DOM比较轻,真实DOM比较重,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性 3.虚拟DOM最终会被react转换为真实DOM,呈现在界面上 DOM:文档对象模型。DOM 为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构。目的其实就是为了能让js操作html元素而制定的一个规范。 4.react引用的js文件 ①react.development.js react核心库 ②react-dom.development.js 支持react操作DOM ③babel.min.js 将jsx转换为jS ④prop-types.js 用于对组件标签属性进行限制 5.jsx语法规则 ①定义虚拟DOM时不要写引号 ②标签中混入JS表达式要用{} ③样式的类名不要用class要用className ④内联样式要用style={{key:value}}的形式去写,font-size等要用fontSize小驼峰样式 ⑤虚拟标签必须只有一个根标签 ⑥标签必须闭合 ⑦标签首字母 (1)若小写字母开头,则将标签转换为html中同名元素,若html中无该标签对应的同名元素.则报错 (2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错 6.区分jS代码(语句)和jS表达式 ①表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方 下面这些都是表达式 (1)a (2)a+b (3)demo(1) (4)arr.map() (5)function test(){} ②语句(代码): 下面这些都是语句(代码): (1)if(){} (2)for(){} (3)switch(){} 7.模块和组件,模块化和组件化 ①模块 js文件 作用:复用js,简化js,提高js运行效率 ②组件 实现局部功能效果的代码和资源的合集(html/css/js/image等等) ③模块化 当应用的js都以模块来编写的,就是一个模块化的应用 ④组件化 当应用以多组件的方式实现,这个应用就是一个组件化的应用 8.函数组件(适用于简单组件[无state的组件]的定义) //1.创建函数式组件 function MyComponent(){ return <h2>函数组件</h2> } //2.渲染组件到界面 (组件首字母大写,标签闭合) ReactDOM.render(<MyComponent/>,document.getElementById('test')); react工作顺序:1.解析组件标签,之后寻找MyComponent组件 2.发现组件是使用函数定义的,随后调用该函数,将虚拟DOM转换为真实DOM,随后呈现在界面中. 9.类式组件(适用于复杂组件[有state的组件]的定义) //1.创建类式组件 必须继承react中的一个类(React.Component) class MyComponent extends React.Component(){ //必须有render render(){ //render中的this代表MyComponent的实例对象<=>MyComponent的组件实例对象 //有返回值 return <h2>类式组件</h2> } } //2.渲染组件到界面 (组件首字母大写,标签闭合) ReactDOM.render(<MyComponent/>,document.getElementById('test')); react工作顺序:1.解析组件标签,之后寻找MyComponent组件 2.发现组件是使用类定义的,随后new出该类实例,并通过该实例调用原型上的render方法. 3.将render返回的虚拟DOM转换为真实DOM,随后呈现在界面中. 10. 组件实例的三大核心属性 前端解构赋值 const [a,b,c] = [1,2,3] const {name,age} = {name:'小明',age:18} 10.1 state 理解:1)state是组件对象的重要属性,值必须是对象(可以包含多个key-value的组合) 2)组件被称为'状态机',通过更新组件的state来更新对应界面显示(重新渲染组件) 注意:1)组件render中的this是组件实例对象 2)组件自定义的方法中的this为undefined,如何解决 a.强制绑定this:通过函数对象bind(); b.箭头函数(开发中使用箭头函数) 3)状态数据不能直接修改要使用setState方法 如下方代码 标准写法: class Weather extrends React.Component{ //构造器 调用1次 constructor(props){ //放到最前方 super(props) //初始化状态 this.state = {isHot:false,wind:'微风'} //解决changeWeather中的this指向,并挂在在实例对象上 this.changeWeather = this.changeWeather.bind(this); } //调用1+n次,1是初始化的那次,n是状态更新的次数 render(){ //this为组件实例对象(即:Weather) retrun <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热':'凉爽'}</h1> } //点几次调用几次 changeWeather(){ //changeWeather放到了Weather的原型对象上,供实例使用 //由于changeWeather作为onClick的回调,不是通过实例调用,是直接调用 //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined //获取原来isHot的值 const isHot = this.state.isHot //严重注意:状态通过setState进行更新,且更新是一种合并不是替换 this.setState({isHot:!isHot}) //严重注意:状态(isHot)不能直接更改 //错误写法this.state.isHot = !isHot; } } ReactDOM.render(<Weather/>,document.getElementById('test')); 精简写法: //1.创建组件 class Weather extrends React.Component{ //初始化状态 state = {isHot:false,wind:'微风'} render(){ retrun <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热':'凉爽'}</h1> } // 自定义方法----要用赋值语句的形式+箭头函数 // 箭头函数没有自身的this会自己找外层的this changeWeather = ()=>{ const isHot = this.state.isHot this.setState({isHot:!isHot}) } } //2.渲染组件到页面 ReactDOM.render(<Weather/>,document.getElementById('test')); 10.2 props 理解:1)每个组件都会有props属性 2)组件标签的所有属性都保存在props中 做用:1)通过标签属性从组件外向组件内传递变化的数据 2)注意:组件内部不能修改props数据 如下方代码: 类式组件使用props: 复杂写法: //1.创建组件 class Person extrends React.Component{ render(){ const {name,age,sex} = this.props //props是只读的 retrun ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年级:{age+1}</li> </ul> ) } } //对标签属性进行类型,必要性的限制 Person.proTypes = { name:ProTypes.string.isRequired, //限制name必传,且为字符窜 sex:ProTypes.string, //限制sex为字符串 age:ProTypes.number, //限制age为数值 speak:ProTypes.func, //限制speak为函数 } //指定默认标签属性值 Person.defaultProps = { sex:'男', //性别默认值为男 age:18,//性别默认值为18 } //2.渲染组件到页面 ReactDOM.render(<Person name="lzq" speak={speak}/>,document.getElementById('test')); 精简写法: //1.创建组件 class Person extrends React.Component{ //对标签属性进行类型,必要性的限制 static proTypes = { name:ProTypes.string.isRequired, //限制name必传,且为字符窜 sex:ProTypes.string, //限制sex为字符串 age:ProTypes.number, //限制age为数值 speak:ProTypes.func, //限制speak为函数 } //指定默认标签属性值 static defaultProps = { sex:'男', //性别默认值为男 age:18,//性别默认值为18 } render(){ const {name,age,sex} = this.props //props是只读的 retrun ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年级:{age+1}</li> </ul> ) } } //2.渲染组件到页面 ReactDOM.render(<Person name="lzq" speak={speak}/>,document.getElementById('test')); 函数式组件使用props: //函数式组件没有state和refs,proTypes只能写在外侧 //1.创建组件 function Person(props){ const {name,sex,age} = props retrun ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年级:{age}</li> </ul> ) } //对标签属性进行类型,必要性的限制 Person.proTypes = { name:ProTypes.string.isRequired, //限制name必传,且为字符窜 sex:ProTypes.string, //限制sex为字符串 age:ProTypes.number, //限制age为数值 speak:ProTypes.func, //限制speak为函数 } //指定默认标签属性值 Person.defaultProps = { sex:'男', //性别默认值为男 age:18,//性别默认值为18 } //2.渲染组件到页面 ReactDOM.render(<Person name="lzq" sex='男' age={18}/>,document.getElementById('test')); 10.3 refs(请勿过度使用refs) 理解:组件内的标签可以定义ref属性来标记自己 10.3.1 字符串形式的ref(不建议使用) //1.创建组件 class Demo extrends React.Component{ render(){ retrun ( <div> <input ref="input1" type="text" placeholder="点击按钮提示数据"> <button onClick={this.showData1}>点击我提示左侧的数据</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"> </div> ) } showData1 = ()=>{ const {input1} = this.refs alter(input1.value) } showData2 = ()=>{ const {input2} = this.refs alter(input2.value) } } //2.渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')); 10.3.2 回调函数形式的ref 回调函数(1)你定义的函数 (2)你没调用 (3)最后别人调用了 render的return中写js代码{} //1.创建组件 class Demo extrends React.Component{ showData1 = ()=>{ const {input1} = this alter(input1.value) } showData2 = ()=>{ const {input2} = this alter(input2.value) } saveInput = (c) => { this.input1 = c } render(){ retrun ( <div> {//注释后面代码:ref回调函数是内联式,在界面更新时执行两次,第一次传null,第二次传DOM元素} <input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据"> {//使用内联式不影响使用,可以使用class类绑定函数的方式避免,以后还是使用内联的比较多} <input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"> <button onClick={this.showData1}>点击我提示左侧的数据</button> <input ref={c => this.input2 = c } onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"> </div> ) } } //2.渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')); 10.3.3 createRef (目前react最推荐的形式) //1.创建组件 class Demo extrends React.Component{ /* React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点.该容器是'专人专用'的 */ //创建ref容器 myRef = React.createRef() myRef2 = React.createRef() //展示左侧输入框的数据 showData1 = ()=>{ alter(this.myRef.current.value) } //展示右侧输入框的数据 showData2 = ()=>{ alter(this.myRef2.current.value) } render(){ retrun ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"> <button onClick={this.showData1}>点击我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"> </div> ) } } //2.渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')); 12. react中的事件处理 (1)通过onXxx属性(如:onClick,onBlur等)指定事件处理函数(注意大小写) a.React使用的是自定义(合成)事件,而不是原生DOM事件(如:onClick是onclick重新封装的事件)---为了更好的兼容性 b.React中的事件是通过事件委托方式处理的(委托给组件最外层元素) ---为了高效 (2)通过event.target得到发生事件DOM元素对象 当发生事件的DOM元素和操作的DOM元素是一个时候可以省略ref(如下面onBlur事件) //1.创建组件 class Demo extrends React.Component{ /* React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点.该容器是'专人专用'的 */ //创建ref容器 myRef = React.createRef() //展示左侧输入框的数据 showData1 = ()=>{ alter(this.myRef.current.value) } //展示右侧输入框的数据 showData2 = (event)=>{ alter(event.target.value) } render(){ retrun ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"> <button onClick={this.showData1}>点击我提示左侧的数据</button> <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"> </div> ) } } //2.渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')); 13.收集表单数据 13.1 非受控组件(页面内所有输入类DOM现用现取) //1.创建组件 class Login extrends React.Component{ handleSubmit = (event) => { event.preventDefault() //阻止表单提交 const {username,password} = this alter(`你输入的用户名是:$(username.value),你输入的密码是:$(password.value)`) } render(){ retrun ( <form onSubmit={this.handleSubmit}> 用户名:<input ref={c => this.username = c} type="text" name="username"> 密码:<input ref={c => this.password = c} type="password" name="password"> <button>登录</button> </form> ) } } //2.渲染组件到页面 ReactDOM.render(<Login/>,document.getElementById('test')); 13.2 受控组件(页面内所有输入类的DOM,随着输入将状态维护在状态里面,需要用的时候从state中取出来) //1.创建组件 class Login extrends React.Component{ //初始化状态 state = { username:'',//用户名 password:'',//密码 } //保存用户名到状态中 saveUsername = (event)=>{ this.setState({username:event.target.value}) } //保存密码到状态中 savePassword = (event)=>{ this.setState({password:event.target.value}) } //表单提交回调 handleSubmit = (event) => { event.preventDefault() //阻止表单提交 const {username,password} = this.state alter(`你输入的用户名是:$(username),你输入的密码是:$(password)`) } render(){ retrun ( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={this.saveUsername} type="text" name="username"> 密码:<input onChange={this.savePassword} type="password" name="password"> <button>登录</button> </form> ) } } //2.渲染组件到页面 ReactDOM.render(<Login/>,document.getElementById('test')); 14.高阶函数和函数的柯里化 /*高阶函数:如果一个函数符合下面规范中的任何一个,那该函数就是高阶函数 1.若A函数,接收的参数是一个函数,那该函数就可以称为高阶函数 2.若A函数调用的返回值依然是一个函数,,那该函数就可以称为高阶函数 常见高阶函数:Promise、setTimeout、arr.map()等等 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式 */ 使用高阶函数和函数柯里化的写法: //1.创建组件 class Login extrends React.Component{ //初始化状态 state = { username:'',//用户名 password:'',//密码 } //保存表单数据 saveFormData = (dataType)=>{ return (event) => { this.setState({[dataType]:event.target.value}) } } //表单提交回调 handleSubmit = (event) => { event.preventDefault() //阻止表单提交 const {username,password} = this.state alter(`你输入的用户名是:$(username),你输入的密码是:$(password)`) } render(){ retrun ( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={this.saveFormData('username')} type="text" name="username"> 密码:<input onChange={this.saveFormData('password')} type="password" name="password"> <button>登录</button> </form> ) } } //2.渲染组件到页面 ReactDOM.render(<Login/>,document.getElementById('test')); 不使用函数柯里化的写法: //1.创建组件 class Login extrends React.Component{ //初始化状态 state = { username:'',//用户名 password:'',//密码 } //保存表单数据 saveFormData = (dataType,event)=>{ this.setState({[dataType]:event.target.value}) } //表单提交回调 handleSubmit = (event) => { event.preventDefault() //阻止表单提交 const {username,password} = this.state alter(`你输入的用户名是:$(username),你输入的密码是:$(password)`) } render(){ retrun ( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={ event => this.saveFormData('username',event)} type="text" name="username"> 密码:<input onChange={ event => this.saveFormData('password',event)} type="password" name="password"> <button>登录</button> </form> ) } } //2.渲染组件到页面 ReactDOM.render(<Login/>,document.getElementById('test')); 14.react的生命周期 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子 14.1 生命周期(旧) 14.1.1 初始化阶段(挂载时) ①constructor() //构造器 ②componentWillMount() //组件将要挂载 ③render() //初始化渲染 ④componentDidMount() //组件挂载完毕 14.1.2 更新阶段 setState() 更新 ①shouldComponentUpdate() //控制组件更新的阀门 ②componentWillUpdate() //组件将要更新 ③render() //状态更新之后执行 ④componentDidUpdate() //组件更新完毕 forceUpdate() 强制更新 ①componentWillUpdate() //组件将要更新 ②render() //状态更新之后执行 ③componentDidUpdate() //组件更新完毕 父组件重新render触发 ①componentWillReceiveProps() //组件将要接受新的props的钩子 ②shouldComponentUpdate() //控制组件更新的阀门 ③componentWillUpdate() //组件将要更新 ④render() //状态更新之后执行 ⑤componentDidUpdate() //组件更新完毕 14.1.3 卸载组件 由ReactDOM.unmountComponentAtNode()触发 componentWillUnmount() //组件将要卸载 14.2 生命周期(新) 新的和旧的相比 ①新的可能弃用componentWillMount、componentWillUpdate、componentWillReceiveProps 不要使用,如果使用新版本需要在这三个前面加UNSAFE_ 如:UNSAFE_componentWillMount 不是不安全而是乱用容易出现bug ②新的新提出两个getDerivedStateFromProps和getSnapshotBeforeUpdate getDerivedStateFromProps 当state的值在任何时候都取决于props,使用场景罕见 14.2.1 初始化阶段(挂载时) ①constructor() //构造器 ②getDerivedStateFromProps() ③render() //初始化渲染 ④componentDidMount() //组件挂载完毕 ----常用 一般在这个钩子中做一些初始化的事情,例如:开启定时器、发送网路请求、订阅消息 14.2.2 更新阶段 setState()和父组件重新render触发 ①getDerivedStateFromProps() //衍生 ①shouldComponentUpdate() //控制组件更新的阀门 ③render() //状态更新之后执行 ④getSnapshotBeforeUpdate() //在更新之前获取快照 ④componentDidUpdate() //组件更新完毕 forceUpdate() 强制更新 ①getDerivedStateFromProps() //衍生 ②render() //状态更新之后执行 ③getSnapshotBeforeUpdate() //在更新之前获取快照 ④componentDidUpdate() //组件更新完毕 14.2.3 卸载组件 由ReactDOM.unmountComponentAtNode()触发 componentWillUnmount() //组件将要卸载 ----常用 一般在这个钩子中做一些收尾的事情,例如:关闭定时器、取消订阅消息 getSnapshotBeforeUpdate使用场景: //1.创建组件 class NewsList extrends React.Component{ //初始化状态 state = {newsArr:[]} componentDidMount(){ setInterval(()=>{ //获取原状态 const {newArr} = this.state //模拟一条新闻 const news = '新闻' + (newArr.length+1) },1000) } getSnapshotBeforeUpdate(){ return this.refs.list.scrollHeight } ④componentDidUpdate(preProps,preState,height){ this.refs.list.scrollTop += this.refs.list.scrollHeight - height } render(){ retrun ( <div className="list" ref="list"> { this.state.newArr.map((n,index)=>{ return <div key={index} className="news">{n}</div> }) } </div> ) } } //2.渲染组件到页面 ReactDOM.render(<NewsList/>,document.getElementById('test')); 15.DOM的diffing算法 15.1 虚拟DOM中key的作用: 1)简单说:key是虚拟DOM的标识,在更新显示时key起着及其重要的作用 2)详细说:当前状态中的数据发生变化时,react会根据[新数据]生成[新的虚拟DOM],随后React进行[新虚拟DOM]与[旧虚拟DOM]的diff比较,比较规则如下: a.旧虚拟DOM中找到了与新虚拟DOM相同的key: (1)若虚拟DOM中内容没变,直接使用之前的真是DOM (2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面之前的真实DOM b.旧虚拟DOM中未找到与新虚拟DOM相同的key 根据数据创建新的真实DOM,随之渲染到界面 15.2 用index作为key可能会引发的问题: 1)若对数据进行:逆序添加、逆序删除等操作: 会产生没有必要的真实DOM更新 ==>界面效果没问题,但效率低了 2)如果结构中包含输入类DOM: 会产生错位DOM更新 ==>界面有问题 3)注意:如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的 15.3 开发中如何选择key? 1)最好使用每条数据的唯一标识作为key,比如id、手机号、省份证号等 2)如果确定只是简单的展示数据,用index也是可以的
16.父子组件之间的通信:
父组件给子组件传递数据:通过props
子组件给父组件传递数据:父组件给子组件传一个函数,当子组件要给父组件传东西的时候,通过props调用一下该函数
17.defaultChecked和checked的区别,defaultChecked只有在第一次指定的时候才起作用,以后不起作用
18.状态在哪里,操作状态的方法就在哪里