1.起步
1.React安装
npm i react react-dom <div id="root"></div> <!-- 1 引入js文件 --> <script src="./node_modules/react/umd/react.development.js"></script> <script src="./node_modules/react-dom/umd/react-dom.development.js"></script> <script> // 2 创建react元素 // 参数一:元素名称 // 参数二:元素属性 // 参数三:元素的子节点 const title = React.createElement('h1', null, 'Hello React') // 3 渲染react元素 // 参数一:要渲染的react元素 // 参数二:挂载点 ReactDOM.render(title, document.getElementById('root')) </script>
2.安装脚手架
npx create-react-app my-app
导入react
import React from 'react'
import ReactDOM from 'react-dom'
3.JSX 的基本使用
JSX自动补全html标签
const title = <h1>hello jsx</h1>
注意点
JS表达式
/* JSX中使用JavaScript表达式 */ const name = 'Jack' const age = 19 const title = ( <h1> Hello JSX, {name}, 年龄:{age} </h1> )
JSX中使用JavaScript表达式的注意点:
import React from 'react' import ReactDOM from 'react-dom' /* JSX中使用JavaScript表达式的注意点: */ // 函数调用表达式 const sayHi = () => 'Hi~' const dv = <div>我是一个div</div> const title = ( <h1> Hello JSX <p>{1}</p> <p>{'a'}</p> <p>{1 + 7}</p> <p>{3 > 5 ? '大于' : '小于等于'}</p> <p>{sayHi()}</p> {dv} {/* 错误演示 */} {/* <p>{ {a: '6'} }</p> */} {/* { if (true) {} } */} {/* { for (var i = 0; i < 10; i++) {} } */} </h1> ) // 渲染react元素 ReactDOM.render(title, document.getElementById('root'))
结果
条件渲染
const isLoading = true // 逻辑与运算符: const loadData = () => { return isLoading && (<div>loading...</div>) } const title = ( <h1> 条件渲染: {loadData()} </h1> )
const isLoading = true // if-else const loadData = () => { if (isLoading) { return <div>loading...</div> } return <div>数据加载完成,此处显示加载后的数据</div> }
const isLoading = false // 三元表达式: const loadData = () => { return isLoading ? (<div>loading...</div>) : (<div>数据加载完成,此处显示加载后的数据</div>) }
列表渲染
/* 列表渲染: */ // 歌曲列表: const songs = [ {id: 1, name: '痴心绝对'}, {id: 2, name: '像我这样的人'}, {id: 3, name: '南山南'}, ] const list = ( <ul> {songs.map(item => <li key={item.id}>{item.name}</li>)} </ul> ) // 渲染react元素 ReactDOM.render(list, document.getElementById('root'))
css
4.React组件
创建组件的两种方法
函数组件
const Hello=()=> <div>函数组件</div> // 渲染react元素 ReactDOM.render(<Hello/>, document.getElementById('root')) 类组件
class Hello extends React.Component { render() { return ( <div>第一个类组件</div> ) } } // 渲染react元素 ReactDOM.render(<Hello/>, document.getElementById('root'))
5.React 事件处理
事件绑定
类组件事件绑定
class App extends React.Component { // 事件处理程序 handleClick() { console.log('单击事件触发了') } render() { return ( <button onClick={this.handleClick}>点我,点我</button> ) } }
函数组件事件绑定
function App() { // 事件处理程序 function handleClick() { console.log('函数组件中的事件绑定,事件触发了') } return ( <button onClick={handleClick}>点我</button> ) }
事件对象
class App extends React.Component { handleClick(e) { // 阻止浏览器的默认行为 e.preventDefault() console.log('a标签的单击事件触发了') } render() { return ( <a href="http://itcast.cn/" onClick={this.handleClick}>zjhhhhh</a> ) } }
6.有状态组件和无状态组件
state的基本使用
class App extends React.Component { /* constructor() { super() // 初始化state this.state = { count: 0 } } */ // (es6)简化语法初始化state(推荐) state = { count: 10 } render() { return ( <div> <h1>计数器:{ this.state.count }</h1> </div> ) } } setState的基本使用 class App extends React.Component { state = { count: 0, test: 'a' } render() { return ( <div> <h1>计数器:{ this.state.count }</h1> <button onClick={() => { this.setState({ count: this.state.count + 1 }) // 错误!!! // this.state.count += 1 }}>+1</button> </div> ) } }
7.this指向问题
JSX中掺杂过多JS逻辑代码,会显得非常混乱,所以我们将逻辑抽离到单独的方法中,保证JSX 结构清晰
如下列代码
class App extends React.Component { state = { count: 0 } // 事件处理程序 onIncrement() { console.log('事件处理程序中的this:', this) this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <h1>计数器:{ this.state.count }</h1> <button onClick={this.onIncrement}>+1</button> {/* <button onClick={() => { this.setState({ count: this.state.count + 1 }) }}>+1</button> */} </div> ) } }
但是运行会报错
原因是事件处理程序中this的值为undefined,我们希望this指向组件实例(render方法的this即为组件实例)
有三种解决方法
1.箭头函数
class App extends React.Component { constructor() { super() this.state = { count: 0 } this.onIncrement = this.onIncrement.bind(this) } // 事件处理程序 onIncrement() { console.log('事件处理程序中的this:', this) this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <h1>计数器:{ this.state.count }</h1> <button onClick={this.onIncrement}>+1</button> </div> ) } } 2.Function.prototype.bind() class App extends React.Component { constructor() { super() this.state = { count: 0 } this.onIncrement = this.onIncrement.bind(this) } // 事件处理程序 onIncrement() { console.log('事件处理程序中的this:', this) this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <h1>计数器:{ this.state.count }</h1> <button onClick={this.onIncrement}>+1</button> </div> ) } }
3.class的实例方法
class App extends React.Component { state = { count: 0 } // 事件处理程序 onIncrement = () => { console.log('事件处理程序中的this:', this) this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <h1>计数器:{ this.state.count }</h1> <button onClick={this.onIncrement}>+1</button> </div> ) } }
8.表单处理
受控组件 class App extends React.Component { state = { txt: '' } handleChange = e => { this.setState({ txt: e.target.value }) } render() { return ( <div> <input type="text" value={this.state.txt} onChange={this.handleChange} /> </div> ) } }
class App extends React.Component { state = { txt: '', content: '', city: 'bj', isChecked: false } handleChange = e => { this.setState({ txt: e.target.value }) } // 处理富文本框的变化 handleContent = e => { this.setState({ content: e.target.value }) } // 处理下拉框的变化 handleCity = e => { this.setState({ city: e.target.value }) } // 处理复选框的变化 handleChecked = e => { this.setState({ isChecked: e.target.checked }) } render() { return ( <div> {/* 文本框 */} <input type="text" value={this.state.txt} onChange={this.handleChange} /> <br/> {/* 富文本框 */} <textarea value={this.state.content} onChange={this.handleContent}></textarea> <br/> {/* 下拉框 */} <select value={this.state.city} onChange={this.handleCity}> <option value="sh">上海</option> <option value="bj">北京</option> <option value="gz">广州</option> </select> <br/> {/* 复选框 */} <input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked} /> </div> ) } }
多表单元素的优化
class App extends React.Component { state = { txt: '', content: '', city: 'bj', isChecked: false } handleForm = e => { // 获取当前DOM对象 const target = e.target // 根据类型获取值 const value = target.type === 'checkbox' ? target.checked : target.value // 获取name const name = target.name this.setState({ [name]: value }) } render() { return ( <div> {/* 文本框 */} <input type="text" name="txt" value={this.state.txt} onChange={this.handleForm} /> <br/> {/* 富文本框 */} <textarea name="content" value={this.state.content} onChange={this.handleForm}></textarea> <br/> {/* 下拉框 */} <select name="city" value={this.state.city} onChange={this.handleForm}> <option value="sh">上海</option> <option value="bj">北京</option> <option value="gz">广州</option> </select> <br/> {/* 复选框 */} <input type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.handleForm} /> </div> ) } }
非受控组件
class App extends React.Component { constructor() { super() this.txtRef=React.createRef() } gettxt=()=> { console.log('文本框的值为:',this.txtRef.current.value); } render() { return ( <div> <input type="text" ref={this.txtRef}/> <button onClick={this.gettxt}>获取文本框的值</button> </div> ) } }
9. props
const Hello = props => { console.log('props:', props) props.fn() return ( <div> <h1>props:</h1> {props.tag} </div> ) } ReactDOM.render( <Hello name="rose" age={19} colors={['red', 'green', 'blue']} fn={() => console.log('这是一个函数')} tag={<p>这是一个p标签</p>} />, document.getElementById('root') )
组件通讯3种方式
子传父
class Parent extends React.Component { state = { parentMsg: '' } // 提供回调函数,用来接收数据 getChildMsg = data => { console.log('接收到子组件中传递过来的数据:', data) this.setState({ parentMsg: data }) } render() { return ( <div className="parent"> 父组件:{this.state.parentMsg} <Child getMsg={this.getChildMsg} /> </div> ) } } // 子组件 class Child extends React.Component { state = { msg: '刷抖音' } handleClick = () => { // 子组件调用父组件中传递过来的回调函数 this.props.getMsg(this.state.msg) } render() { return ( <div className="child"> 子组件:{' '} <button onClick={this.handleClick}>点我,给父组件传递数据</button> </div> ) } } ReactDOM.render(<Parent />, document.getElementById('root'))
兄弟组件
// 父组件 class Counter extends React.Component { // 提供共享状态 state = { count: 0 } // 提供修改状态的方法 onIncrement = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <Child1 count={this.state.count} /> <Child2 onIncrement={this.onIncrement} /> </div> ) } } const Child1 = props => { return <h1>计数器:{props.count}</h1> } const Child2 = props => { return <button onClick={() => props.onIncrement()}>+1</button> } ReactDOM.render(<Counter />, document.getElementById('root'))
props校验
安装prop-types
npm i prop-types
props默认值
10.Context
步骤
(1) 创建context得到两个组件
const { Provider, Consumer } = React.createContext()
(2)用Provider组件包裹根组件(value代表要传的值)
class App extends React.Component { render() { return ( <Provider value="pink"> <div className="app"> <Node /> </div> </Provider> ) } }
(3)目标子组件包裹要Consumer组件 (data就是传过来的值, {data}在标签中渲染data)
const Child = props => { return ( <div className="child"> <Consumer>{data => <span>我是子节点 -- {data}</span>}</Consumer> </div> ) }
11.组件的生命周期
创建时
更新时
setState和forceUpdate()
class App extends React.Component { constructor(props) { super(props) // 初始化state this.state = { count: 0 } } // 打豆豆 handleClick = () => { this.setState({ count: this.state.count + 1 }) } render() { console.warn('生命周期钩子函数: render') return ( <div> <Counter count={this.state.count} /> <button onClick={this.handleClick}>打豆豆</button> </div> ) } } class Counter extends React.Component { render() { console.warn('--子组件--生命周期钩子函数: render') return <h1>统计豆豆被打的次数:{this.props.count}</h1> } }
强制更新
// 演示强制更新: this.forceUpdate() class Counter extends React.Component { render() { console.warn('--子组件--生命周期钩子函数: render') return <h1 id="title">统计豆豆被打的次数:{this.props.count}</h1> } // 注意:如果要调用 setState() 更新状态,必须要放在一个 if 条件中 // 因为:如果直接调用 setState() 更新状态,也会导致递归更新!!! componentDidUpdate(prevProps) { console.warn('--子组件--生命周期钩子函数: componentDidUpdate') // 正确做法: // 做法:比较更新前后的props是否相同,来决定是否重新渲染组件 console.log('上一次的props:', prevProps, ', 当前的props:', this.props) if (prevProps.count !== this.props.count) { this.setState({}) // 发送ajax请求的代码 } // 错误演示!!! // this.setState({}) // 获取DOM // const title = document.getElementById('title') // console.log(title.innerHTML) } }
卸载时
class Counter extends React.Component { componentDidMount() { // 开启定时器 this.timerId = setInterval(() => { console.log('定时器正在执行~') }, 500) } render() { return <h1>统计豆豆被打的次数:{this.props.count}</h1> } componentWillUnmount() { console.warn('生命周期钩子函数: componentWillUnmount') // 清理定时器 clearInterval(this.timerId) } }
12.render-props和高阶组件
render props模式
class Mouse extends React.Component { // 鼠标位置state state = { x: 0, y: 0 } // 鼠标移动事件的事件处理程序 handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } // 监听鼠标移动事件 componentDidMount() { window.addEventListener('mousemove', this.handleMouseMove) } render() { // return null return this.props.render(this.state) } } class App extends React.Component { render() { return ( <div> <h1>render props 模式</h1> <Mouse render={mouse => { return ( <p> 鼠标位置:{mouse.x} {mouse.y} </p> ) }} /> </div> ) } }
把鼠标变成一只猫
<Mouse render={mouse => { return ( <img src={img} alt="猫" style={{ position: 'absolute', top: mouse.y - 64, left: mouse.x - 64 }} /> ) }} /> </div> ) } }
更推荐用children属性代替render属性,即在Mouse组件嵌套一个子节点
<Mouse> {mouse => ( <img src={img} alt="猫" style={{ position: 'absolute', top: mouse.y - 64, left: mouse.x - 64 }} /> )} </Mouse>
组件中的render函数也要改变
render() { return this.props.children(this.state) }
代码优化
高阶组件
创建高阶组件 // 创建高阶组件 function withMouse(WrappedComponent) { // 该组件提供复用的状态逻辑 class Mouse extends React.Component { // 鼠标状态 state = { x: 0, y: 0 } handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } // 控制鼠标状态的逻辑 componentDidMount() { window.addEventListener('mousemove', this.handleMouseMove) } componentWillUnmount() { window.removeEventListener('mousemove', this.handleMouseMove) } render() { return <WrappedComponent {...this.state} /> } } return Mouse }
包装组件
const Position = props => ( <p> 鼠标当前位置:(x: {props.x}, y: {props.y}) </p> ) // 猫捉老鼠的组件: const Cat = props => ( <img src={img} alt="" style={{ position: 'absolute', top: props.y - 64, left: props.x - 64 }} /> ) // 获取增强后的组件: const MousePosition = withMouse(Position) // 调用高阶组件来增强猫捉老鼠的组件: const MouseCat = withMouse(Cat) 渲染包装后的组件 class App extends React.Component { render() { return ( <div> <h1>高阶组件</h1> {/* 渲染增强后的组件 */} <MousePosition /> <MouseCat /> </div> ) } } displayName // 创建高阶组件 function withMouse(WrappedComponent) { // 该组件提供复用的状态逻辑 class Mouse extends React.Component { // 鼠标状态 state = { x: 0, y: 0 } handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } // 控制鼠标状态的逻辑 componentDidMount() { window.addEventListener('mousemove', this.handleMouseMove) } componentWillUnmount() { window.removeEventListener('mousemove', this.handleMouseMove) } render() { return <WrappedComponent {...this.state} /> } } // 设置displayName Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}` return Mouse } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component' }
传递props
const MousePosition = withMouse(Position)
往被包装的组件传递props,这时被包装的组件拿不到这个props
<MousePosition a="1" />
需要高阶组件传给他
render() { console.log('Mouse:', this.props) return <WrappedComponent {...this.state} {...this.props} /> }