Hooks API
useState
使用方法:const [count, setCount] = useState(0); 可以传入一个参数作为这个状态的默认值。
函数组件的写法
const App = () => {
const [count, setCount] = useState(0);
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count})
</div>
);
};
class组件的写法
class App extends React.Component {
state = {
count: 0,
};
render() {
return (
<div
onClick={() => {
this.setState({ count: (this.state.count += 1) });
}}
>
Click(count:{this.state.count})
</div>
);
}
}
useState方法的原理
let state;
const myUseState = (initVal) => {
state = state === undefined ? initVal : state;
const setState = (newVal) => {
state = newVal;
render();
};
return [state, setState];
};
const render = () => {
ReactDOM.render(<App />, document.getElementById("root"));
};
在单个组件中可以使用有多个状态值,所以state应该是一个数组,上述代码改写为:
let state = [];
let index = 0;
const myUseState = (initVal) => {
const currentIndex = index;
state[currentIndex] =
state[currentIndex] === undefined ? initVal : state[currentIndex];
const setState = (newVal) => {
state[currentIndex] = newVal;
render();
};
index += 1;
return [state[currentIndex], setState];
};
const render = () => {
index = 0;
ReactDOM.render(<App />, document.getElementById("root"));
};
const App = () => {
const [count, setCount] = myUseState(0);
const [count2, setCount2] = myUseState(1);
return (
<>
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count})
</div>
<div
onClick={() => {
setCount2(count2 * 2);
}}
>
Click2(count2:{count2})
</div>
</>
);
};
useState必须按照固定的顺序被调用
useState()方法里面唯一的参数就是初始state。
useState()方法返回值是一个数组,分别为当前 state 以及更新 state 的函数。
为了验证useState的调用顺序的影响,把使用方法改写为如下:
// const [count, setCount] = useState(0);
let countArr = useState(0);
let count = countArr[0];
let setCount = countArr[1];
下面代码不按照固定顺序调用state,导致结果不对:
let id = 0;
const App = (props) => {
let count = 0;
let setCount;
let name = "lily";
let setName;
if (id % 2) {
[count, setCount] = myUseState(0);
[name, setName] = myUseState("mike");
} else {
[name, setName] = myUseState("lily");
[count, setCount] = myUseState(0);
}
id += 1;
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count},name:{name})
</div>
);
};
useState必须按照固定的数量被调用
let id = 0;
const App = (props) => {
let count = 0;
let setCount;
let name = "lily";
let setName;
let age = 0;
let setAge;
if (id % 2) {
[count, setCount] = myUseState(0);
[name, setName] = myUseState("mike");
[age, setAge] = myUseState(18);
} else {
[name, setName] = myUseState("lily");
[count, setCount] = myUseState(0);
}
id += 1;
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count},name:{name},age:{age})
</div>
);
};
上述代码用的是自己写的myUseState方法实现的,实际上用useState方法,直接报错:
useStatue的默认值支持函数赋值,来延迟初始化数据的默认值
const App = () => {
const [count, setCount] = useState(0);
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count})
<Child vocation="教师" />
</div>
);
};
const Child = (props) => {
const [name, setName] = useState("lily");
const [work, setWork] = useState(() => {
return props.vocation || "学生";
});
return (
<div>
<p>姓名:{name}</p>
<p>职业:{work}</p>
</div>
);
};
useEffect
使用方法:
useEffect(() => {
// 具体操作
})
函数组件的写法
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = count;
});
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count})
</div>
);
};
class组件的写法
class App extends React.Component {
state = {
count: 0,
};
render() {
return (
<div
onClick={() => {
this.setState({ count: (this.state.count += 1) });
}}
>
Click(count:{this.state.count})
</div>
);
}
componentDidMount() {
document.title = this.state.count;
}
componentDidUpdate() {
document.title = this.state.count;
}
}
默认情况下,useEffect在第一次渲染之后和每次更新之后都会执行
useEffect第二个参数
useEffect第二个参数是一个非必要的数组。第二个参数不传,组件每一次都执行;第二个参数传空数组,组件仅会执行一次。第二个参数传非空数组,只有该数组中的值都不变化时才不会执行。
useEffect返回函数
每个 useEffect 都可以返回一个函数。这是 effect 可选的清除机制。React会在组件卸载的时候执行清除操作。
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("useEffect");
document.title = count;
});
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count})
<br />
{count % 2 ? <Child /> : null}
</div>
);
};
const Child = (props) => {
const [name, setName] = useState("lily");
const [work, setWork] = useState(() => {
return props.vocation || "学生";
});
useEffect(() => {
return () => {
console.log("unMount");
};
});
return (
<div>
<p>姓名:{name}</p>
<p>职业:{work}</p>
</div>
);
};
useContext
使用方法:const count = useContext(CountContext);
其中CountContext是使用createContext创建的上下文对象(const CountContext = createContext());每个context对象都会返回一个Provider组件,provider接收一个value属性,传递给消费组件。
const CountContext = createContext(0);
const App = () => {
const [count, setCount] = useState(0);
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count})
<CountContext.Provider value={count}>
<CountChild />
</CountContext.Provider>
</div>
);
};
const CountChild = () => {
const count = useContext(CountContext);
return <div>子组件{count}</div>;
};
useMemo
使用方法:useMemo(() => {}, []),把函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
const App = () => {
const [count, setCount] = useState(0);
let double = useMemo(() => {
console.log("useMemo");
return count * 2;
});
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count}, double: {double})
</div>
);
};
useMemo的用法类似useEffect,第一个参数是一个函数,第二个参数是这个函数相关的逻辑变量组成的一个数组。与useEffect类似,第二个参数不传,组件每一次都执行;第二个参数传空数组,组件仅会执行一次。第二个参数传非空数组,只有该数组中的值都不变化时才不会执行。
与useEffect不同的是,执行时机不同。useEffect是在渲染后执行的,useMemo是在渲染时期执行的,返回的值可以直接参与渲染。
useCallback
当useMemo返回的值是一个函数时,可以使用useCallback替代,即useMemo(() => fn) === useCallback(fn)
const App = () => {
const [count, setCount] = useState(0);
let double = useMemo(() => {
return () => {
return count * 2;
};
});
let double2 = useCallback(() => {
return count * 2;
});
return (
<div
onClick={() => {
setCount(count + 1);
}}
>
Click(count:{count}, double:{double()}, double2:{double2()})
</div>
);
};
useRef
使用方法:const couterRef = useRef(initValue)
获取子组件或者DOM节点的句柄
const App = () => {
const [count, setCount] = useState(0);
const countRef = useRef();
useEffect(() => {
console.log(countRef.current);
});
return (
<div
onClick={() => {
setCount(count + 1);
}}
ref={countRef}
>
Click(count:{count})
</div>
);
};
渲染周期之间共享数据的存储
const App = () => {
const [count, setCount] = useState(0);
const timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
setCount((count) => {
return count + 1;
});
}, 1000);
}, []);
useEffect(() => {
if (count > 5) {
clearInterval(timer.current);
}
});
return <div>Click(count:{count})</div>;
};
自定义hook
自定义Hooks可以实现逻辑复用等,在多个组件中可以复用我们自定义的Hooks,并且里面的状态是独立的,自定义Hooks以use开头。
自定义的获取浏览器窗口的大小:
const useWinSize = () => {
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
const onResize = useCallback(() => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
}, []);
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
return size;
};
Class组件与hooks的对比
Class组件的不足
1、状态逻辑复用难
缺少复用机制
属性props和高阶组件导致层级冗余
2、复杂组件难以理解
生命周期函数混杂不相干逻辑
相干逻辑分散在不同生命周期
3、this指向困扰
类成员函数不能保证this(在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this;虽然掌握this是必要,但是处理起来依然很麻烦)
Hooks的优势
1、函数组件无this指向问题
2、自定义hook方便复用状态逻辑
3、副作用(绑定事件、网络请求、访问DOM)的关注点分离