(1)组件数据源
props是组件对外接口,上层组件通过props把数据和方法传给下层组件
state是对内接口,是私有的,用来组件维护自己的私有状态
视图层:当props和state变化时,react会重新渲染
(2)组件间传值类型检查
类型检查有两种写法,一种是写在组件内,一种写在外面,功能相同
class Student extends React.Component {
//添加静态属性,react自带的
static defaultProps = {
single: '保密'
}
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
}
constructor(props) {
super(props)
console.log(props);
}
render() {
// console.log(this.props);
let { name, age, single } = this.props
return (
<ul>
<li>name:{name}</li>
<li>age:{age}</li>
<li>single:{single}</li>
</ul>
)
}
}
//defaultProps发生在 constructor构造函数执行之前
//defaultProps是react自带的,不需要引入库
//该静态属性可以添加默认值
Student.defaultProps = {
single: '保密'
}
//传值类型检查
Student.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
}
const student = {
name: 'jack',
age: 18
}
ReactDOM.render(<Student {...student} />, document.getElementById('app'))
propType是检查传值类型的工具,规范传值的数据类型和是否必要,如果要用的话要先引用
<script src="./js/prop-types.js"></script>
React里有自带的属性defaultProps,它是一个对象,可以给参数传默认值
(3)task案例
单组件案例
class App extends React.Component {
//数据
state = {
tasks: [],
task: '',
date: new Date().toLocaleDateString()
}
//同步输入框
handelChange = (e) => {
this.setState({
task: e.target.value
//利用回调函数解决setState异步的问题
}, () => {
// console.log("task", this.state.task)
})
}
//添加任务
handleAddTask = (e) => {
const { task, tasks } = this.state
//使任务不为空
if (!task) {
return
}
//unshift返回值是新数组的长度 不是新的数组
tasks.unshift(task)
this.setState({
tasks: tasks,
task: ' ',
//保证时间是最新同步的
date: new Date().toLocaleDateString()
}, () => { })
}
// 删除任务
handleDelete(index) {
const { tasks } = this.state
//1指删除的个数
tasks.splice(index, 1)
this.setState({
tasks:tasks
}, () => { })
}
render() {
return (
<div>
<h1>Today Tasks : {this.state.tasks.length}</h1>
<div>
<input type="text" value={this.state.task} onChange={this.handelChange} />
<button onClick={this.handleAddTask}>添加</button>
</div>
<div>
<ul>
{
//可以传两个参数,一个数组项,一个序号
this.state.tasks.map((task, index) => {
return (
<li key={index}>
<span>{index + 1}--{task}--{this.state.date}</span>
{/*通过bind改变this指向,传入index值给函数使用*/}
<button onClick={this.handleDelete.bind(this, index)}>delete</button>
</li>
)
})
}
</ul>
</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
多组件构建思路
结构
App根组件
添加组件,列表组件,列表项组件
数据位置
某个组件自己需要,直接定义到自己组件里;若共同需要,则定义到父组件里
行为
数据在哪,修改数据的行为就在哪,其他组件通过调用来触发方法
组件间通信
子组件修改父组件利用props
组件类型
若有自己的数据则定义成类组件
多组件案例
class App extends React.Component {
state = {
tasks: []
}
//添加 task为AddTask组件传来参数
addTask = (task) => {
const { tasks } = this.state
//使任务不为空
if (!task) {
return
}
//使每个任务都有独立的时间
let taskNew = { task: task, date: new Date().toLocaleTimeString() }
// unshift 是新数组的长度 不是新的数组
tasks.unshift(taskNew)
this.setState({
tasks: tasks,
})
}
//删除
deleteTask = (index) => {
const { tasks } = this.state
tasks.splice(index, 1)
this.setState({
tasks
}, () => { })
}
render() {
return (
<div>
<h1>Today Tasks : {this.state.tasks.length}</h1>
<AddTask addTask={this.addTask} />
<TaskList tasks={this.state.tasks} deleteTask={this.deleteTask} />
</div>
)
}
}
//添加任务组件
class AddTask extends React.Component {
state = { task: '' }
//输入框同步
handelChange = (e) => {
this.setState({
task: e.target.value
}, () => { })
}
handleAddTask = () => {
const { task } = this.state
this.props.addTask(task)
this.setState({
task: ''
})
}
render() {
return (
<div>
<input type="text" onChange value={this.state.task} onChange={this.handelChange} />
<button onClick={this.handleAddTask}>添加</button>
</div>
)
}
}
//任务列表组件
function TaskList(props) {
return (
<div>
<ul>
{
// item是一个对象
props.tasks.map((item, index) => {
return <TaskItem key={index} index={index} task={item.task} date={item.date} deleteTask={props.deleteTask} />
})
}
</ul>
</div>
)
}
//单项任务组件
function TaskItem(props) {
function handleDelete() {
props.deleteTask(props.index)
}
return (
<li>
<span>{props.index + 1}--{props.task}--{props.date}</span>
<button onClick={handleDelete}>delete</button>
</li>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
(4)context (发布订阅模式)
有时我们需要多层嵌套才能完成需求,如果只用props传值太过于繁琐,context就能很好的解决这个问题,它不仅能传值,还能传方法
const themes = {
light: {
color: '#9e9e9e',
background: '#eeeeee'
},
dark: {
color: '#e0e0e0',
background: '#222222'
}
}
//创建发布订阅组件
const { Provider, Consumer } = React.createContext({ theme: themes.light })
class App extends React.Component {
state = { theme: themes.light }
handleClick = () => {
const theme = this.state.theme === themes.light ? themes.dark : themes.light
this.setState({
theme: theme
})
}
render() {
// 1:Provider组件通过value提供给包裹子组件发布值
// 允许消费者订阅context变化,value变化时Consumer会重新渲染
// 当一个组件有多个Provider提供的上下文,则最后一个会覆盖其他的
// 被Provider包裹组件的的所有所包裹的组件全可以用到Provider发布的值
// 本质上:this.context = Provider所提供的value值
return (
<div>
{/*
默认值是一个对象,为了组件能成功解析被Provider包裹和不被包裹两种情况,
给value也设置成对象,而且key值要与默认值里的key相同
*/}
{/*第一个是对象,第二个是方法*/}
<Provider value={{ theme: this.state.theme, changeTheme: this.handleClick }}>
<h1 style={{ color: this.state.theme.color, background: this.state.theme.background }}>content</h1>
<ThemeButton />
<Child1 />
</Provider>
<Child1 />
</div>
)
}
}
function ThemeButton() {
return (
<Consumer>
{
(obj) => {
console.log('button' + obj);
//注意对象和方法的接收方式不同
return <button style={{ ...obj.theme }} onClick={obj.changeTheme}>改变主题</button>
}
}
</Consumer>
)
}
function Child1(props) {
//2:Consumer里有一个表达式,表达式里有一个函数,函数里有一个返回值
//这个返回值才是最终渲染出来的组件
return (
<div>
<Consumer>
{
(obj) => {
console.log(obj)
//obj就是传递过来的参数对象,自动解析
return <h1 style={{ ...obj.theme }}>Child1</h1>
}
}
</Consumer>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
多个provider时解决方案
const MoneyContext = React.createContext(0)
const HouseContext = React.createContext('')
class Father extends React.Component {
state = { money: 50, house: 'apartment' }
render() {
return (
<MoneyContext.Provider value={this.state.money}>
<HouseContext.Provider value={this.state.house} >
<p>father money: {this.state.money}</p>
<p>father house: {this.state.house}</p>
<Son />
</HouseContext.Provider>
</MoneyContext.Provider>
)
}
}
class Son extends React.Component {
//contextType和Consumer配合使用
//添加一个静态属性,使组件能获取第一个Provider所提供的值
static contextType = MoneyContext
render() {
console.log(this.context);
const money = this.context
const house = this.context
return (
<div>
<p>son :获取father money : {money}</p>
<HouseContext.Consumer>
{
(house) => {
console.log(house);
return <p>{house}</p>
}
}
</HouseContext.Consumer>
</div>
)
}
}
ReactDOM.render(<Father />, document.getElementById("app"));
context-task案例
//传递默认值
const { Provider, Consumer } = React.createContext({
task: [],
addTask: () => { },
deleteTask: () => { }
})
//根组件
class App extends React.Component {
//直接写的话this上只有简单数据没有方法
// state = {
// tasks:[],
// addTask:this.addTask,
// deleteTask:this.deleteTask
// }
constructor() {
super()
//必须经过挂载才能把方法一起传进去
this.state = {
tasks: [],
addTask: this.addTask,
deleteTask: this.deleteTask
}
}
//添加task为AddTask组件传来参数
addTask = (task) => {
const { tasks } = this.state
if (!task) {
return
}
let taskNew = { task: task, date: new Date().toLocaleTimeString() }
tasks.unshift(taskNew)
this.setState({
tasks: tasks,
})
}
//删除
deleteTask = (index) => {
const { tasks } = this.state
tasks.splice(index, 1)
this.setState({
tasks
}, () => { })
}
render() {
return (
<div>
<h1>Today Tasks : {this.state.tasks.length}</h1>
<Provider value={this.state}>
<AddTask />
<TaskList />
</Provider>
</div>
)
}
}
//添加任务组件
class AddTask extends React.Component {
state = { task: '' }
//输入框同步
handelChange = (e) => {
this.setState({
task: e.target.value
}, () => { })
}
handleAddTask(addTask) {
const { task } = this.state
addTask(task)
//清楚input
this.setState({
task: ''
})
}
render() {
return (
<div>
<input type="text" onChange value={this.state.task} onChange={this.handelChange} />
<Consumer>
{
//解构赋值,吧addTask拿出来
({ addTask }) => {
//bind改变this指向
return <button onClick={this.handleAddTask.bind(this, addTask)}>添加</button>
}
}
</Consumer>
</div>
)
}
}
//任务列表组件
function TaskList(props) {
return (
<div>
<Consumer>
{
({ tasks }) => {
return <ul>
{
tasks.map((item, index) => {
return <TaskItem key={index} index={index} task={item.task} date={item.date} />
})
}
</ul>
}
}
</Consumer>
</div>
)
}
//单项任务组件
function TaskItem(props) {
// function handleDelete() {
// props.deleteTask(props.index)
// }
return (
<li>
<span>{props.index + 1}--{props.task}--{props.date}</span>
<Consumer>
{
({ deleteTask }) => {
return <button onClick={() => {deleteTask(props.index)}}>删除</button>
}
}
</Consumer>
</li>
)
}
ReactDOM.render(<App />, document.getElementById('app'))