Почему опасно использовать query_posts()

query_posts() предназначена для самого WordPress и должна использоваться только для изменения основного запроса WordPress на базе которого потом строится Цикл WordPress.

Поэтому, когда нужно создать еще один запрос (цикл), нужно использовать get_posts() или WP_Query. Эти функции могут принимать все те же параметры что и query_posts().

Пример использования get_posts() вместо query_posts():

<?php
$args = array( 'posts_per_page' => 3 );

$lastposts = get_posts( $args );

foreach( $lastposts as $post ){
	setup_postdata( $post );
	?>
	<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
	<?php
	the_content();
}

wp_reset_postdata();

Пример использования WP_Query вместо query_posts():

$args = array( 'posts_per_page' => 3 );
$query = new WP_Query( $args );

while ( $query->have_posts() ) {
	$query->the_post();

	the_title(); // выведем заголовок поста
}

wp_reset_postdata();

Почему query_posts() нужно использовать очень аккуратно?

Основной запрос WordPress — это запрос, который выполняется до вывода какого-либо текста на страницу (на раннем этапе). Параметры такого запроса собирает сам WP опираясь на: запрошенный URL; настройки ЧПУ (постоянных ссылок) и др. Так WordPress определяет сколько записей показать на странице, записи из какой рубрики показывать и т.д.

Дополнительный запрос — это тот, который создается пользователем (темой, плагином) и выполняется дополнительно к основному. Например: Вывод в сайдбаре списка популярных записей; Вывод комментируемых записей.

Так вот, если использовать query_posts() для создания дополнительных запросов (циклов), то могут появится ошибки из-за которых будет рушится структура кода, так как повторный вызов query_posts() переписывает базовый запрос WP, на основе которого определяется тип текущей страницы (запись, рубрика), определяется сколько записей показывать на странице (пагинация) и многое другое.

Разница между query_posts() и WP_Query в том, что query_posts() создает новый WP_Query объект и связывает его с глобальной переменной $wp_query, тогда как get_posts() или WP_Query() создает новый WP_Query объект, без изменения каких-либо глобальных данных.

Давайте рассмотрим на примере:

// Пример 1: Дополнительный запрос с помощью query_posts
query_posts( $args );
while ( have_posts() ) {
	the_post();

	the_title(); // вывести название
}

// Пример 2: Дополнительный запрос с помощью WP_Query
$second_query = new WP_Query( $args );
while ( $second_query->have_posts() ) {
	$second_query->the_post();

	the_title();
}

Тут, первый пример переписывает глобальные переменные $wp_query, $post, $posts, а второй работает автономно и ничего глобального не трогает.

Чтобы более детально понять разницу прочитайте заметку: 3 способа построения циклов в WordPress.

Если вы по каким-то причинам вынуждены использовать query_posts(), то после цикла обязательно используйте wp_reset_query() - сбрасывает глобальные переменные запроса на начальные.

Если не сбросить цикл с помощью wp_reset_query(), то, например, может сломаться пагинация (это самый яркий пример из жизни) — это когда первая страница работает, а остальные показывают 404 страницу.